Static initializer doesn't run during JUnit tests
Asked Answered
T

3

9

I have an interesting JUnit problem here (JUnit 4.12). I have a base class that only has static methods. They have to be static, because of the way they're used. I inherit other classes from the base class. So, if the base class is Base, we have ChildA and ChildB.

Most of the methods are contained in the base class, but it has to know which child it actually is (just calling the methods as the base class is invalid). This is done via a static data member in the base class:

public class Base {

    protected static ChildType myType = ChildType.Invalid;
    ...
}    

Each child sets the data member via 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 a configuration name).

This all works perfectly when running the application. Stepping through it in the debugger and through log messages, I can see the appropriate types are set and the methods load the appropriate configurations based on the child type.

The problem arises when using JUnit. We have some JUnit tests to test each of the base class methods. Since calling the methods on just the base class is invalid, we call the methods on the child classes, thus:

bool result = ChildA.methodTwo();

This ''always fails''. Why? The static initializer never gets called. When running the code as an application, it gets 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 is JUnit doing that skips the static initializer? Is there a way around it?

Details

In reality, we're not calling the method as I posted above. I just wanted the example to be as clear as possible. In reality, we have a Web Service written with the Jersey framework. The method called is one of the REST endpoints.

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

And we call it like this (sorry about the ugly syntax, it's just the way 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 the GET tests work, and the static initializer is called on all of them. It's just this POST that fails, and only when running the JUnit test.

Taegu answered 24/10, 2017 at 15:26 Comment(10)
"the base class knows what type it is" How does it know what type it is? Do you pass some sort of a "type token", or do you rely on some other mechanism?Jehias
is 'myType' in basic class? if so it is set by some random child Class that was loaded last, and it is not reliable. static initializers must be called, no way it is not called.Cooe
It knows what type it is because I call the static method with the classes' name: ChildA.methodOne(). At that point--at least when running the code normally--it calls the static iniitalizer and sets the static data member. The data member identifies the classes' actual type.Taegu
@KysilIvan: myType is set to ChildType.Invalid in the base class. The static initializers in the child classes set it to a valid type (e.g. ChildType.ChildA or ChildType.ChildB). Once again, this works when running as a regular application. It's only skipped when running this one test via JUnit.Taegu
@Taegu Is myType a member of Base then?Jehias
Yes. It is a protected member of the base class. I've updated the question to clarify this.Taegu
it is not correct to init myType in static initializers as they called only once on class loading, and this is not quite predictable. you can add abstract method Type getType() in base class and implement it in Child classes.Cooe
Let me give that a shot.Taegu
@KysilIvan He can't do that, because his methods are all static.Jehias
For this kind of use, only one class (A or B) can be safely loaded into JVM. One way to address this is to convert the static methods to pass-through to instance methods on a static instance, and then set it to A or B as neededAcidimeter
T
0

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

Let me reiterate: the code, as I had it, works perfectly when run as an application. Only when running via JUnit does it fail.

So now I have something similar to the below:

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 can't override static methods, the version of the method in the base class has a different signature (methodTwoBase() instead of methodTwo). I tried it as a regular application and in JUnit and it works both ways.

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

Taegu answered 24/10, 2017 at 17:47 Comment(0)
J
5

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

[myType is] a protected member of the base class

Relying on static initializers to set static fields in the base class is very fragile, because multiple subclasses "compete" for a single field in the base class. This "locks in" the behavior of the base class into the behavior desirable for the subclass whose initializer ran last. Among other bad things, it denies a possibility of using multiple subclasses along with the Base class, and makes it possible for ChildA.methodTwo() to run functionality designed for ChildB.methodTwo(). In fact, there is no ChildA.methodTwo() and ChildB.methodTwo(), there's only Base.methodTwo() that relies on information prepared for it by the static initialization sequence.

There are several solutions to this problem. One possibility is to pass Class<Child###> object to 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 the callers would 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 static implementation with non-static, and use "regular" polymorphic behavior in conjunction with singletons 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");
Jehias answered 24/10, 2017 at 16:12 Comment(3)
And that is precisely what I wanted to avoid. Passing the actual type I want to a method obliviates all the elegance of OOP and inheritance.Taegu
@Taegu Alas, there's no static inheritance in Java, hence no elegance of OOP or inheritance, only the annoyances of unreliable code. Singleton approach (the one at the bottom of the answer) may work better, because now you could have derived classes overriding methods of the base class.Jehias
@Frecklefoot: I would argue that instance methods of singleton objects are far more elegant than static methods anyway.Monandrous
R
2

There is only one Base.myType field shared amongst all accessors: Base, ChildA and ChildB. The following sequence of events could cause the failures you are seeing:

  • JUnit test invoking ChildA.methodOne() starts execution, causing the JVM classloader to load ChildA.class and execute its static initializer block, setting Base.myType to ChildType.ChildA,
  • JUnit test invoking ChildB.methodOne() starts execution, causing the JVM classloader to load ClassB.class and execute its static initializer block, setting Base.myType to ChildType.ChildB, then
  • JUnit test invoking ChildA.methodTwo() starts execution, not executing the ChildA static initializer block first as ChildA has already been loaded by the JVM classloader, resulting in the JUnit test failing because Base.myType (and thus ChildA.myType) presently equals ChildType.ChildB.

The basic design issue is that part of your code expects the child types to own the myType field but that field is in fact shared by all child types.

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


addendum: Thanks for clarifying in comments that you only have one JUnit test invoking just ChildA.methodTwo() which is only defined in Base, not ChildA. What is happening is likely the JVM deciding that ChildA need not be initialized just to call its parent Base class's methodTwo() method. @ShyJ provides a very nice explanation of this for parent and child static field access at https://mcmap.net/q/700081/-java-static-initialization-with-inheritance. I believe that something similar is happening in your JUnit test.


addendum 2: Below is my code modeling and reproducing the described issue of myType having the value ChildType.Invalid during the JUnit test to the best of current understanding:

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);
    }
}

Output of execution of ChildATest.test():

result: true
Base.myType: Invalid
Rimester answered 24/10, 2017 at 16:0 Comment(8)
In my testing (right now), I am only running the one test, so no other Child Types are being invoked. And, like I mentioned, the base class type (ChildType.Invalid) is the type, not ChildType.ClassB when I expect ChildType.ClassA.Taegu
Does the method methodTwo() exist in both Base and ChildA as a static method? If yes you may be running into something like https://mcmap.net/q/15603/-why-doesn-39-t-java-allow-overriding-of-static-methods.Rimester
No, methodTwo() only exists in Base. So when you call ChildA.methodTwo(), it is actually calling Base.methodTwo(), but myType allows it to call the correct configuration.Taegu
Thanks for that clarification. In my testing that means that only the Base static field initializer gets called - the ChildA static initializer is not called at all. The JVM spec interpretation of access of base class static fields via child class names at https://mcmap.net/q/700081/-java-static-initialization-with-inheritance seems to provide a decent explanation of what is happening here. In brief: calling a base class's static method via the name of a child class does not trigger class load (and static initialization) of the child class.Rimester
That sounds reasonable, except this code works when run as a regular application. It's just when run as a JUnit test is when it fails. There's clearly something screwy with JUnit and the way it runs things.Taegu
Does the code I've provided accurately represent the code under test and the failing JUnit test? If no please elaborate. Also, please provide a simplified and complete (compilable, runnable) form of the working regular application and the failing JUnit test. That would help others or myself can help you out a bit more with this.Rimester
Yes, your code is a fair replication of what I'm doing in my test (although I would change Base.myType to ChildA.myType and it would still return Invalid). I've posted my own answer to this problem. I can't mark it as the answer (yet), but it solved the problem.Taegu
Thanks for answering my question and your own second question, "Is there a way around it?". Did I at least answer your first question "What is JUnit doing that skips the static initializer?" :)Rimester
T
0

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

Let me reiterate: the code, as I had it, works perfectly when run as an application. Only when running via JUnit does it fail.

So now I have something similar to the below:

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 can't override static methods, the version of the method in the base class has a different signature (methodTwoBase() instead of methodTwo). I tried it as a regular application and in JUnit and it works both ways.

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

Taegu answered 24/10, 2017 at 17:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.