Home > Software engineering >  Spring not autowiring when run from JUnit
Spring not autowiring when run from JUnit

Time:12-21

I am testing ControllerAdvice class where i have EncodeUtil is autowired, this is always resolved as Null when I run using JUnit test, no issue when run via spring boot application, what am i missing here?

@ControllerAdvice
public class Base64EncodedResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Autowired
    private EncodeUtil encodeUtil;

    @Override
    public boolean supports(MethodParameter returnType, 
                            Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> converterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {

        if(returnType.getMethod().getName().equalsIgnoreCase("doPNMImage"))
        {
           encodeUtil.encodeBase64(body);
           response.getHeaders().add("ENCODE_TYPE", "Base64");
        }
        else return body;
    }
}

Here is my Junit Class, i have

@ExtendWith(SpringExtension.class)
@WebMvcTest(PNMImageController.class)
@AutoConfigureMockMvc
public class Base64EncodedResponseBodyAdviceTest {

    @Mock
    private EncodeUtil encodeUtil;

    @InjectMocks
    PNMImageController pnmImageController;

    @BeforeEach
    void init(){
      MockitoAnnotations.initMocks(pnmImageController);
      mockMvc = MockMvcBuilders.standaloneSetup(pnmImageController)
            .setControllerAdvice(Base64EncodedResponseBodyAdvice.class)
            .build();
    }

    @Test
    public void getAuthorizeTest() throws Exception {
        ReflectionTestUtils.setField(encodeUtil, "salt", SALT);
        Mockito.when(this.encodeUtil.encodeBase64()).thenReturn(TEST_BASE64_STR);
        mockMvc.perform(post("http://localhost:8080/image/doPNMImage"))
                .andExpect(status().isOk())
                .andExpect(content().string(TEST_BASE64_STR))
                .andExpect(header().string("ENCODE_TYPE", "Base64"));
    }
}

The test fails with Nullpointer exception

Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "com.ylm.cnpl.core.util.EncodeUtil.encodeBase64(String)" because "this.encodeUtil" is null
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "com.ylm.cnpl.core.util.encodeBase64(String)" because "this.encodeUtil" is null
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    

CodePudding user response:

@WebMvcTest-javadoc:

Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not @Component, @Service or @Repository beans).


If we want EncodeUtil as a bean in our tests: Import(configuration), configure/component-scan it.

  • easiest: use @SpringBootTest instead of @WebMvcTest.

  • to keep the test context "slim", we should:

    • Dedicate a (main) configuration for the affected bean(s) & import them into test.

    • Or/and dedicate a "test" configuration, easiest with:

      @ExtendWith(SpringExtension.class)
      @WebMvcTest(PNMImageController.class)
      @AutoConfigureMockMvc
      public class Base64EncodedResponseBodyAdviceTest {
      
        @Configuration
        static class CustomTestConfig {
          @Bean
          EncodeUtil  ...               
        }
        ...
      } 
      

If we want @InjectMocks to work, we should (try) @MockBean instead of @Mock... Difference between @Mock, @MockBean and Mockito.mock() ... Difference between @Mock and @InjectMocks ...

In , I would use: (org.springfr...)@MockBean and @Autowired !;)

CodePudding user response:

Please add @SpringBootTest more: https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications and you need specify which one configuration to load @ContextConfiguration (https://docs.spring.io/spring-boot/docs/1.4.0.M3/reference/htmlsingle/#boot-features-testing-spring-boot-applications-detecting-config)

CodePudding user response:

As you are trying to test one controller and controllerAdvice, it is a correct choice to use a @WebMvcTest(PNMImageController.class)

With @WebMvcTest, you rely on Spring to create the desired controller. You attempted to create a second one with @InjectMocks, but the one that Spring tries to build cannot be constructed.

See @WebMvcTest javadoc

Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans but not @Component, @Service or @Repository beans).

You need to provide the EncodeUtil to Spring. As you intend to use it a mock, a correct annotation to use is @MockBean

Secondly, you create an instance of mockMvc. There is no need to do it - you explicitly wrote @AutoConfigureMockMvc, so you can autowire it into your test. In fact @WebMvcTest is annotated with @AutoConfigureMockMvc, so you don't need it in your test. Same thing for @ExtendWith(SpringExtension.class).

Updated code

@WebMvcTest(PNMImageController.class)
public class Base64EncodedResponseBodyAdviceTest {

    @MockBean
    private EncodeUtil encodeUtil;

    @Autowired
    MockMvc mockMvc;


    @Test
    public void getAuthorizeTest() throws Exception {
        // ReflectionTestUtils.setField(encodeUtil, "salt", SALT);
        Mockito.when(this.encodeUtil.encodeBase64()).thenReturn(TEST_BASE64_STR);
        mockMvc.perform(post("http://localhost:8080/image/doPNMImage"))
                .andExpect(status().isOk())
                .andExpect(content().string(TEST_BASE64_STR))
                .andExpect(header().string("ENCODE_TYPE", "Base64"));
    }
}

On top of that:

  • setting salt field on encodeUtil looks suspicious. You shouldn't need to set fields on a mock.

IMHO changing your test to @SpringBootTest is not the way to go: SpringBootTest is intended for integration testing, it will spin up your entire application. Of course it will get the job done - your controller will be spinned up as well, but you should strive to keep your tests as lean as possible - spinning only the desired controller will make your test faster and less brittle.

  • Related