从 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 JUnit:如何在自动装配组件中模拟自动装配组件
Spring Boot 自动装配配置类进入 Junit 测试
使用 Spring Boot 2.1.15.RELEASE(junit 4) 进行集成测试的 Bean 自动装配为 null