从 JUnit 运行时 Spring 不会自动装配

Posted

技术标签:

【中文标题】从 JUnit 运行时 Spring 不会自动装配【英文标题】:Spring not autowiring when run from JUnit 【发布时间】:2022-01-21 19:27:45 【问题描述】:

我正在测试我有 EncodeUtil 的 ControllerAdvice 类是自动装配的,当我使用 JUnit 测试运行时,这总是被解析为 Null,通过 Spring Boot 应用程序运行时没有问题,我在这里缺少什么?

@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;
    

这是我的 Junit 课程,我有

@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"));
    

测试失败,出现 Nullpointer 异常

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)
    

【问题讨论】:

【参考方案1】:

@WebMvcTest-javadoc:

使用此注解将禁用完全自动配置,而是应用仅与 MVC 相关的配置测试(即@Controller、@ControllerAdvice、@JsonComponent、Converter/GenericConverter、过滤器、WebMvcConfigurer 和 HandlerMethodArgumentResolver bean 但不是 @Component、@Service 或 @Repository bean)


如果我们希望 EncodeUtil 在我们的测试中作为 bean:导入(配置),配置/组件扫描它。

最简单:使用@SpringBootTest 代替@WebMvcTest

为了保持测试上下文“精简”,我们应该:

为受影响的 bean 和import 指定一个(主)配置进行测试。

或/和专用“测试”配置,最简单的是:

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

  @Configuration
  static class CustomTestConfig 
    @Bean
    EncodeUtil  ...               
  
  ...
 

如果我们想让@InjectMocks 工作,我们应该(尝试)@MockBean 而不是@Mock... Difference between @Mock, @MockBean and Mockito.mock() ... Difference between @Mock and @InjectMocks ...

在spring-test,我会使用:(org.springfr...)@MockBean@Autowired !;)

【讨论】:

谢谢您的回复,我的目的是测试ControllerAdvice在调用控制器的Post方法时会被调用,使用@SpringBootTest如何启动控制器的REST方法 -> spring.io/guides/gs/testing-web ! ;) (MockMvc 或 TestRestTemplate)【参考方案2】:

当您尝试测试一个控制器和 controllerAdvice 时,使用@WebMvcTest(PNMImageController.class) 是正确的选择

使用@WebMvcTest,您可以依靠 Spring 创建所需的控制器。 您尝试使用 @InjectMocks 创建第二个,但 Spring 尝试构建的那个无法构建。

参见@WebMvcTest javadoc

使用此注解将禁用完全自动配置,而是仅应用与 MVC 测试相关的配置(即 @Controller、@ControllerAdvice、@JsonComponent、Converter/GenericConverter、Filter、WebMvcConfigurer 和 HandlerMethodArgumentResolver bean,但不应用 @Component、@Service 或@Repository bean)。

您需要将EncodeUtil 提供给 Spring。 当您打算将其用作模拟时,要使用的正确注释是 @MockBean

其次,您创建一个 mockMvc 实例。 无需这样做 - 您明确编写了@AutoConfigureMockMvc,因此您可以将其自动装配到您的测试中。事实上@WebMvcTest 是用@AutoConfigureMockMvc 注解的,所以你的测试中不需要它。 @ExtendWith(SpringExtension.class) 也是如此。

更新代码

@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"));
    


最重要的是:

在 encodeUtil 上设置盐域看起来很可疑。您不需要在模拟上设置字段。

恕我直言,将您的测试更改为 @SpringBootTest 不是可行的方法: SpringBootTest 用于集成测试,它将启动您的整个应用程序。当然,它会完成工作——你的控制器也会被启动,但你应该努力让你的测试尽可能精简——只旋转所需的控制器会让你的测试更快,更不脆弱。

【讨论】:

【参考方案3】:

请添加 @SpringBootTest 更多:https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications 并且您需要指定要加载哪个配置 @ContextConfiguration (https://docs.spring.io/spring-boot/docs/1.4.0.M3/reference/htmlsingle/#boot-features-testing-spring-boot-applications-detecting-config)

【讨论】:

通过添加@SpringBootTest 会引发以下错误Configuration error: found multiple declarations of @BootstrapWith for test class [com.ylm.cnpl.core.util.Base64EncodedResponseBodyAdviceTest]: [@org.springframework.test.context.BootstrapWith(value=org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper), @org.springframework.test.context.BootstrapWith(value=org.springframework.boot.test.context.SpringBootTestContextBootstrapper)]

以上是关于从 JUnit 运行时 Spring 不会自动装配的主要内容,如果未能解决你的问题,请参考以下文章

Spring 3 和 JUnit 4(自动装配)

Spring JUnit:如何在自动装配组件中模拟自动装配组件

Spring:在Junit中加载的类中自动装配不同的类

Spring Boot 自动装配配置类进入 Junit 测试

使用 Spring Boot 2.1.15.RELEASE(junit 4) 进行集成测试的 Bean 自动装配为 null

在 JUnit 5 测试中模拟 Spring Boot 2 应用程序的自动装配依赖项