javaspring-bootunit-testingspring-boot-test

Best practice for writing unit test of a custom Spring ApplicationRunner implementation and passing variations of ApplicationArguments to it


I have an ApplicationRunner implementation named RefAppRunner which performs some logic.

I would like to write a SpringBoot based unit test to validate that ApplicationRunner behaves as expected based on the ApplicationArguments it gets.

The implementation also has some @Autowired fields.

What is the recommended way of unit testing such a component in a Spring Boot application?

I want all the Spring based context to be initialized as though the app is launched properly, but I want to supply variations of the ApplicationArguments

Is there a recommended way to write such tests?

I have commented out @SpringBootTest because if it is enabled, then the custom ApplicationRunner will have been invoked before the run test method is invoked .. meaning there is no control over the values passed to the impl.

Here is my current Java unit test in a Spring Boot project:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.DefaultApplicationArguments;
import org.springframework.boot.test.context.SpringBootTest;

//@SpringBootTest
public class RefAppRunnerTest
{
  @Autowired
  private RefAppRunner refAppRunner;

  @Test
  public void run() throws Exception {
    refAppRunner.run(new DefaultApplicationArguments(new String[]{}));
  }
}

RefAppRunner is an ApplicationRunner implementation:

@Component
public class RefAppRunner implements ApplicationRunner {

  @Override
  public void run(ApplicationArguments args) {
    // ..
  }
}

But I'm getting this error:

java.lang.NullPointerException: Cannot invoke "com.apps.referenceapp.RefAppRunner.run(org.springframework.boot.ApplicationArguments)" because "this.refAppRunner" is null
  at com.apps.referenceapp.RefAppRunnerTest.run(RefAppRunnerTest.java:16)

Solution

  • This a good case use case for ApplicationContextRunner (Spring Docs):

    public class AppRunnerTest {
    
        @Test
        public void testAppRunner() {
            ApplicationContextRunner contextRunner = new ApplicationContextRunner();
    
            contextRunner
                    .withUserConfiguration(RefAppRunner.class)
                    .run(context -> {
                        Assertions.assertTrue(context.containsBean("refAppRunner"));
                        RefAppRunner myAppRunner = (RefAppRunner) context.getBean("refAppRunner");
                        myAppRunner.run(new DefaultApplicationArguments());
                    });
        }
    }
    

    Please note that this will configure a new ApplicationContext for the class that only contains your RefAppRunner. If your RefAppRunner has a dependency on e.g. a "MyFancyBean" bean, you will need to supply it to the context as well or the autowiring inside RefAppRunner will fail:

            //...
            contextRunner
                    .withUserConfiguration(RefAppRunner.class, MyFancyBean.class)
            //...