2

I'm using the maven-surefire-plugin with junit 4.1.4. I have a unit test which relies on a 3rd party class that internally uses static { ... } code block to initiate some variables. For one test, I need to change one of these variables, but only for certain tests. I'd like this block to be re-executed between tests, since it picks up a value the first time it runs.

When testing, it seems like surefire instantiates the test class once, so the static { ... } code block is never processed again.

This means my unit tests that change values required for testing are ignored, the static class has already been instantiated.

Note: The static class uses System.loadLibrary(...), from what I've found, it can't be rewritten to be instantiated, static is the (rare, but) proper usage.

I found a similar solution for Spring Framework which uses @DirtiesContext(...) annotation, allowing the programmer to mark classes or methods as "Dirty" so that a new class (or in many cases, the JVM) is initialized between tests.

How do you do the same thing as @DirtiesContext(...), but with maven-surefire-plugin?

public class MyTests {
    @Test
    public void test1() {
        assertThat(MyClass.THE_VALUE, is("something-default"));
    }

    @Test
    public void test2() {
        System.setProperty("foo.bar", "something-else");
        assertThat(MyClass.THE_VALUE, is("something-else"));
        //                            ^-- this assert fails
        //                                value still "something-default"
    }
}
public class MyClass {
    static {
        String value;
        if(System.getProperty("foo.bar") != null) {
            value = System.getProperty("foo.bar"); // set to "something-else"
        } else {
            value = "something-default";
        }
    }
    public static String THE_VALUE = value;

}
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>4.1.2</version>
      </plugin>
tresf
  • 7,103
  • 6
  • 40
  • 101
  • Surefire has very little to do with it - it’s the test engine that controls the lifecycle of test classes (JUnit, JUnit5 etc). You can ask surefire to fork out multiple JVMs - but that’s slow. – Boris the Spider Aug 04 '21 at 20:53
  • @BoristheSpider I updated the question to specify that I'm using Junit 4. Does that help clarify the question? I'm ok forking another JVM, it's how this would be done in practice. In regards to `surefire-maven-plugin` versus `JUnit`, I'm mostly just using `@Test` annotation for these, so I'm still unsure how to fix the static class problem. I've also added a pseudocode snippet. – tresf Aug 04 '21 at 20:58
  • Also, `reuseForks` set to `false` seems to be broken: https://issues.apache.org/jira/browse/SUREFIRE-1534. **Edit:** Bumping to Surefire 3.0.0-M4 fixes this. – tresf Aug 04 '21 at 21:12
  • Unfortunately bumping to Surefire 3.0.0-M4 and setting `reuseForks` false doesn't fix this, since it's the same test class, it still fails. Is there another way? – tresf Aug 04 '21 at 21:17
  • Can't you put the test methods in different test classes so that [``'s](https://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#reuseForks) "_If set to "false", a new VM is forked for each test class to be executed._" applies? – Gerold Broser Aug 04 '21 at 22:33
  • @GeroldBroser Yes, but this is a small project with only 2 unit tests. Creating separate classes is a bit overkill. – tresf Aug 05 '21 at 00:39
  • I may take this opportunity to just [remove the `static` initializer](https://stackoverflow.com/a/21163499/3196753), but I've seen enough projects to leverage them that I don't see this problem going away. – tresf Aug 05 '21 at 00:43
  • Ok... unfortunately this class uses `System.loadLibrary(...)` so the `static` invocation is static to the JVM regardless of the class design. I'll move the test to another class for now, unless there are some other ideas. – tresf Aug 05 '21 at 01:06
  • Re "overkill": It doesn't matter how...small...your code is if it doesn't work. ;) – Gerold Broser Aug 05 '21 at 01:16
  • I don't think it's polite to minimize things like this. If one needs to run 12 unit tests that impact this same static block, it requires 12 separate classes to do so. It also affects `@Test` reusability for each class, which is especially grotesque (or more accurately, inflated) in scenarios where tests are otherwise identical. I chose the word "overkill" as a short way to describe the impact of this design. I think it fairly accurately represents how onlooking developers would feel if put the same situation. – tresf Aug 05 '21 at 01:47
  • Please relax. And see the ;) ... no attack was intended. I can delete the comment if you want. – Gerold Broser Aug 05 '21 at 02:08
  • Please leave it, it adds context to the following question. – tresf Aug 05 '21 at 03:06
  • I've added a note to the question about `System.loadLibrary(...)` to explain why `static` is (rarely but in this case correctly) being used. – tresf Aug 05 '21 at 16:25
  • 2
    Maybe [this answer to _SuppressStaticInitializationFor(Powermock)_](https://stackoverflow.com/a/29674083/1744774) is helpful. – Gerold Broser Aug 05 '21 at 23:13
  • Quoting: "PowerMock is a Java framework that allows you to unit test code normally regarded as untestable.". Yeah, this is probably the framework to use. It does use bytecode injection techniques, which seems like it could add its own issues (possible blocking newer JDKs too), but sharing knowledge of the framework is greatly appreciated. It seems to be exactly for this type of use-case. – tresf Aug 06 '21 at 01:36

1 Answers1

0

static initialization blocks in java are something that can't be easily handled by JUnit. In general static stuff doesn't play nicely with unit testing concepts.

So, assuming you can't touch this code, your options are:

Option 1:

Spawn a new JVM for each test - well, this will work, but might be an overkill because it will aggravate the performance

If you'll follow this path, you might need to configure surefire plugin with:

forkCount=1
reuseForks=false

According to the surefire plugin documentation this combination will execute each test class in its own JVM process.

Option 2:

Create a class with a different class loader for every test.

Basically in Java if class com.foo.A is created by ClassLoader M is totally different than the same class com.foo.A created by ClassLoaded N. This is somewhat hacky but should work.

The overhead is much smaller than in option 1. However you'll have to understand how to "incorporate" new class loaders into the testing infrastructure.

For more information about the creation of the custom class loader read for example this tutorial

Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
  • Thanks. The first solution is discussed in detail the OP's comments, but for completeness sake: "if you can't touch this code" the code can be touched, but the class uses `System.loadLibrary(...)` which is EFFECTIVELY static, so reinstantiation doesn't do anything, so the choice was made to leave it alone as it's actually more accurate. For now, adding tests into separate classes and using `false` is the workaround, but I do not consider this a solution. In regards to the class loader, I agree, its hacky and seems more confusing from a unit testing perspective. – tresf Aug 05 '21 at 16:06
  • "In general static stuff doesn't play nicely with unit testing concepts." I don't understand this statement. Something as simple as `java.library.path` is effectively static and can impact many (many) projects, so this `banish all static` pragmatism hurts projects which use real-world, recommended techniques. I'd be happy to reinvoke all unit tests in a for-loop with differentiating properties if that's a possibility. This notion that nothing can be static holds weight for some stuff, not others. Making new classes each time is not the solution, rather a workaround for a plugin behavior. – tresf Aug 05 '21 at 16:15
  • Note, I've also entertained something like `System.unloadLibrary(...)` paradigm to allow a proper un-instantiation but I could not find such an API.:) I've updated the question to explain why `static` is (rare but properly) being used. – tresf Aug 05 '21 at 16:23