0

I want to create a JUnit test for this private method:

@Component
public class ReportingProcessor {

    @EventListener
    private void collectEnvironmentData(ContextRefreshedEvent event) {
    }
}

I tried this:

@SpringBootApplication
public class ReportingTest {

    @Bean
    ServletWebServerFactory servletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    @Test
    public void reportingTest() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {


        GenericApplicationContext parent = new GenericApplicationContext();
        parent.refresh();
        ConfigurableApplicationContext context = new SpringApplicationBuilder(Configuration.class).parent(parent).run();
        ContextRefreshedEvent refreshEvent = new ContextRefreshedEvent(context);        

        ReportingProcessor instance = new ReportingProcessor();

        Method m = ReportingProcessor.class.getDeclaredMethod("collectEnvironmentData", ContextRefreshedEvent.class);
        m.setAccessible(true);
        m.invoke(instance, refreshEvent);               
    }
}

But I get exception: Caused by: org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.

What is the proper way to implement a mocked object for ContextRefreshedEvent?

Peter Penzov
  • 1,126
  • 134
  • 430
  • 808

1 Answers1

1

There is a whole lot of debate/insights on why you should not/avoid writing unit tests for private methods. Have a look at this SO question that should help you decide and provide more insights - How do I test a private function or a class that has private methods, fields or inner classes?

But, if you want to achieve what you have posted anyway; let me break down a few things which are failing the test.

  1. Spring Boot tests need to be annotated with @SpringBootTest (if using JUnit 5) and additionally with @RunWith(SpringRunner.class) if you are using JUnit 4

  2. You should not be creating an instance of your class with the new operator in the test, let the Test context automatically loads it using @Autowired or any other similar mechanism to inject the class.

  3. To mock your input parameter and to invoke the private method you can use Powermockito library. Please note that if your scenario would not have required invoking a private method then mockito library should be enough for almost all mocking scenarios.

Below is the test that should work:

@SpringBootTest
public class ReportingProcessorTest {

    @Autowired
    ReportingProcessor reportingProcessor;

    @Test
    public void reportingTest() throws Exception {

        ContextRefreshedEvent contextRefreshedEvent = PowerMockito.mock(ContextRefreshedEvent.class);
        Whitebox.invokeMethod(reportingProcessor, "collectEnvironmentData", contextRefreshedEvent);

    }
}
Dhawal Kapil
  • 2,584
  • 18
  • 31
  • I get `java.lang.IllegalStateException: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration or @SpringBootTest(classes=...) with your test` – Peter Penzov Oct 16 '19 at 22:53
  • yeah, that's a pretty common error, it occurs when your spring boot test is unable to find either a class that is of type SpringBootApplication or configuration class annotated by @Config, basically, it needs to load the context and needs a location of one of these, more - https://docs.spring.io/spring-boot/docs/1.4.0.M3/reference/htmlsingle/#boot-features-testing-spring-boot-applications-detecting-config – Dhawal Kapil Oct 17 '19 at 04:12
  • Well, I can't find a solution. Any idea how to fix it? – Peter Penzov Oct 17 '19 at 16:25
  • your spring boot project is not correctly setup, I will suggest creating a new project from https://start.spring.io/ just to understand what could be wrong with your project structure – Dhawal Kapil Oct 18 '19 at 15:41