1

How can I run code in my @RunWith(SpringRunner.class) @SpringBootTest(classes = {...}) JUnit test before Spring starts?

This question has been asked several times (e.g. 1, 2) but was always "solved" by some configuration recommendation or other, never with a universal answer. Kindly don't question what I am about to do in that code but simply suggest a clean way to do it.

Tried so far and failed:

Extend SpringJUnit4ClassRunner to get a class whose constructor can run custom code before initializing Spring. Failed because super(testClass) must be called first thing and already does a whole lot of things that get in the way.

Extend Runner to get a class that delegates to SpringRunner instead of inheriting it. This class could run custom code in its constructor before actually instantiating the SpringRunner. However, this setup fails with obscure error messages like java.lang.NoClassDefFoundError: javax/servlet/SessionCookieConfig. "Obscure" because my test has no web config and thus shouldn't meddle with sessions and cookies.

Adding an ApplicationContextInitializer that is triggered before Spring loads its context. These things are easy to add to the actual @SpringApplication, but hard to add in Junit. They are also quite late in the process, and a lot of Spring has already started.

Florian
  • 4,821
  • 2
  • 19
  • 44
  • I tested the "Extend Runner" option and it works for me. You should probably provide more details about your scenario: spring version, minimized and anonimized testcase. For the "Extend SpringJUnit4ClassRunner" option your constructor might look like this: `public MyCustomSpringRunner(Class> clazz) throws InitializationError { super(doSomethingAndPassthrough(clazz)); }`;` where you do your stuff in the static method `doSomethingAndPassthrough` which then returns the class passed in as parameter. – jannis Apr 18 '19 at 13:04

2 Answers2

1

One way to do it is to leave out SpringRunner and use the equivalent combination of SpringClassRule and SpringMethodRule instead. Then you can wrap the SpringClassRule and do your stuff before it kicks in:

public class SomeSpringTest {

    @ClassRule
    public static final TestRule TestRule = new TestRule() {
            private final SpringClassRule springClassRule =
                new SpringClassRule();

            @Override
            public Statement apply(Statement statement, Description description) {
                System.out.println("Before everything Spring does");
                return springClassRule.apply(statement, description);
            }
        };

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Test
    public void test() {
        // ...
    }
}

(Tested with 5.1.4.RELEASE Spring verison)

I don't think you can get more "before" than that. As for other options you could also check out @BootstrapWith and @TestExecutionListeners annotations.

jannis
  • 4,843
  • 1
  • 23
  • 53
0

Complementing jannis' comment on the question, the option to create an alternative JUnit runner and let it delegate to the SpringRunner does work:

public class AlternativeSpringRunner extends Runner {

    private SpringRunner springRunner;

    public AlternativeSpringRunner(Class testClass) {
        doSomethingBeforeSpringStarts();
        springRunner = new SpringRunner(testClass);
    }

    private doSomethingBeforeSpringStarts() {
        // whatever
    }

    public Description getDescription() {
        return springRunner.getDescription();
    }

    public void run(RunNotifier notifier) {
        springRunner.run(notifier);
    }

}

Being based on spring-test 4.3.9.RELEASE, I had to override spring-core and spring-tx, plus javax.servlet's servlet-api with higher versions to make this work.

Florian
  • 4,821
  • 2
  • 19
  • 44