Static initializer does not start during JUnit tests

I have an interesting JUnit problem (JUnit 4.12). I have a base class that only has static methods. They must be static due to how they are used. I inherit other classes from the base class. So, if the base class is Base , we have ChildA and ChildB .

Most methods are contained in the base class, but it should know which one it is (just calling the methods as the base class is not valid). This is done through a static data element in the base class:

 public class Base { protected static ChildType myType = ChildType.Invalid; ... } 

Each child element sets a data member through a static initializer, thus:

 static { myType = ChildType.ChildA; } 

Then, when the methods are called, the base class knows what type it is and loads the appropriate configurations (the type is actually the name of the configuration).

This works fine when starting the application. After going through it in the debugger and through the log messages, I see that the corresponding types are installed, and the methods load the corresponding configurations based on the child type.

The problem occurs when using JUnit. We have some JUnit tests to test each of the base class methods. Since calling methods only in the base class is not valid, we call methods on the child classes, thus:

 bool result = ChildA.methodTwo(); 

It always doesn’t work. What for? A static initializer is never called. When you run the code as an application, it is called, and everyone is happy. When I run it as a JUnit test, the static initializer is skipped and the methods have invalid data. What does JUnit do that the static initializer skips? Is there any way around this?

More details

In fact, we do not name the method, as I wrote above. I just wanted this example to be as clear as possible. In fact, we have a web service written with the Jersey framework. This method is called one of the REST endpoints.

 @POST @Produces(MediaType.TEXT_PLAIN) public String methodPost() { ... return new String( itWorked ? "success" : "fail" ); } 

And we call it that (sorry for the ugly syntax, this is how it works):

 @Test public void testThePost() throws Exception { javax.ws.rs.core.Response response = target("restapi/").request().post(Entity.entity(null, MediaType.TEXT_PLAIN)); assertEquals( 200, response.getStatus() ); } 

All GET tests work, and a static initializer is called for all of them. It is just this POST that fails, and only when running the JUnit test.

+5
source share
3 answers

I decided to try what @Arkdiy suggested, and have pass-through methods in child classes.

Let me reiterate: the code, like mine, works great when run as an application. Only when starting through JUnit does it crash .

So now I have something similar to the following:

 public class BaseClass { protected static ChildType myType = ChildType.Invalid; ... public static boolean methodTwoBase() { ... } } public class ChildA extends BaseClass { public static boolean methodOne() { ... } public static boolean methodTwo() { myType = ChildType.ChildA; return methodTwoBase(); } } public class ChildB extends BaseClass { public static boolean methodOne() { ... } public static boolean methodTwo() { myType = ChildType.ChildB; return methodTwoBase(); } } 

Since I cannot override static methods, the version of the method in the base class has a different signature ( methodTwoBase() instead of methodTwo ). I tried this as a regular application and in JUnit, and it works both ways.

Kind of an interesting problem, and I blame Unit. Thanks for all the input!

0
source

You are trying to implement polymorphic behavior for static methods, a language function that is present in other programming languages ​​but not in Java.

[ myType is] a protected member of the base class

Using static initializers to set static fields in a base class is very fragile because several subclasses "compete" for one field in the base class. This β€œblocks” the behavior of the base class in the desire for behavior for the last subclass to be initiated. Among other bad things, he denies the possibility of using multiple subclasses with the Base class and allows ChildA.methodTwo() to run the functionality intended for ChildB.methodTwo() . Actually there is no ChildA.methodTwo() and ChildB.methodTwo() , there is Base.methodTwo() , which relies on the information prepared for it by a static initialization sequence.

There are several solutions to this problem. One possibility is to pass the Class<Child###> object to the methods of the base class:

 class Base { public static void method1(Class childConfig, String arg) { ... } public static void method2(Class childConfig, int arg1, String arg2) { ... } } 

Now callers will need to change

 ChildA.method1("hello"); ChildA.method2(42, "world"); 

to

 Base.method1(ChildA.class, "hello"); Base.method2(ChildA.class, 42, "world"); 

Another solution would be to replace the static implementation with a non-static one and use β€œregular” polymorphic behavior in combination with single games created in derived classes:

 class Base { protected Base(Class childConfig) { ... } public void method1(String arg) { ... } public void method2(int arg1, String arg2) { ... } } class ChildA extends Base { private static final Base inst = new ChildA(); private ChildA() { super(ChildA.class); } public static Base getInstance() { return inst; } ... // Override methods as needed } class ChildB extends Base { private static final Base inst = new ChildB(); private ChildB() { super(ChildB.class); } public static Base getInstance() { return inst; } ... // Override methods as needed } 

and call

 ChildA.getInstance().method1("hello"); ChildA.getInstance().method2(42, "world"); 
+3
source

Only one Base.myType field is available for all accessories: Base , ChildA and ChildB . The following sequence of events can lead to the failures you see:

  • Making a JUnit call to ChildA.methodOne() starts execution, forcing the JVM ChildA.class to load ChildA.class and execute its static initialization block, setting Base.myType to ChildType.ChildA ,
  • Making a JUnit call to ChildB.methodOne() starts execution, forcing the JVM ClassB.class to load ClassB.class and execute its static initialization block, setting Base.myType to ChildType.ChildB , then
  • Making a JUnit call ChildA.methodTwo() starts the execution, rather than executing the ChildA static initialization block first, since ChildA already loaded by the JVM class loader, which results in a JUnit error, because Base.myType (and therefore ChildA.myType ) is currently equal to ChildType.ChildB .

The main design problem is that part of your code expects the child types to own the myType field, but this field is actually shared by all the child types.

Please indicate in which order your JUnit tests are run in order to verify the theory above. Thanks!


addition . Thank you for clarifying in the comments that you have only one JUnit test calling only ChildA.methodTwo() , which is defined only in Base , not ChildA . The JVM probably decided that ChildA did not need to be initialized to call its parent method Base class methodTwo() . @ShyJ gives a very good explanation for this for accessing the static parent and child fields at fooobar.com/questions/496941 / .... I believe something like this happens in your JUnit test.


appendix 2 . The following is my modeling of the code and the reproduction of the described myType problem with the value ChildType.Invalid during the JUnit test in the best sense of the current time:

 public enum ChildType { Invalid, ChildA } public class Base { protected static ChildType myType = ChildType.Invalid; public static boolean methodTwo() { return true; } } public class ChildA extends Base { static { myType = ChildType.ChildA; } } public class ChildATest { @org.junit.Test public void test() { boolean result = ChildA.methodTwo(); System.out.println("result: " + result); System.out.println("Base.myType: " + Base.myType); } } 

Execution output of ChildATest.test() :

 result: true Base.myType: Invalid 
+2
source

Source: https://habr.com/ru/post/1272852/


All Articles