测试使用 bean 而不是模拟对象
Test used bean instead mock object
我从 Spring
和 Mockito
开始工作。
我使用 JUnit
、Mockito
、Spring boot
。
我已经测试了控制器,并尝试让它工作。
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = CommonConfiguration.class)
@ActiveProfiles(profiles = "embedded")
public class UserController_IntegrationWContainer_Tests {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext wac;
@MockBean(name = "SignatureValidator")
private SignatureValidator signatureValidator;
private ObjectMapper objectMapper;
...
@Before
public void before() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).dispatchOptions(true).build();
this.objectMapper = new ObjectMapper();
}
@Test
public void createConfirmation() throws Exception {
...
ConfirmationDetails signature = new ConfirmationDetails("signature");
when(this.signatureValidator.validateAndEnforceSignature(signature))
.thenReturn("advanced");
mockMvc.perform(post("/distributions/1/receive")
.content(this.objectMapper.writeValueAsBytes(signature))
.contentType(MediaType.APPLICATION_JSON)
.andExpect(status().is(201));
}
此测试class用于测试UserController
。
@RestController
@Validated
@Transactional
public class UserController {
private final static Logger logger = LoggerFactory.getLogger(UserController.class);
private UserService userService;
private SignatureValidator signatureValidator;
...
@Autowired
public UserController(UserService userService, SignatureValidator signatureValidator, ...) {
this.userService = userService;
this.signatureValidator = signatureValidator;
...
}
@RequestMapping(
value = "/distributions/{distributionId}/receive",
method = RequestMethod.POST,
produces = "application/json")
public ResponseEntity<Object> confirmReceipt(
@RequestBody ConfirmationDetails signature,
@Context HttpServletRequest request,
@Digits(fraction = 0, integer = 19, message = "ID cannot exceed 19 digits")
@Min(value = 1, message = "ID must be > 0")
@PathVariable("distributionId") Long distributionId) {
logger.info("Processing request confirmReceipt");
/*for this string I want to use stub. but stub doesn't work*/
String advancedSignature = signatureValidator.validateAndEnforceSignature(signature);
...
Map<String, String> resultsMap = new HashMap<>();
resultsMap.put("code", "RECEIPT_SIGNED");
resultsMap.put("message", "Distribution download confirmed");
return new ResponseEntity<>(resultsMap, HttpStatus.CREATED);
}
}
SignatureValidator 接口:
public interface SignatureValidator {
@SuppressWarnings("unchecked")
String validateAndEnforceSignature(ConfirmationDetails signature);
}
SignatureValidator 的两个实现:
@Component
public class SignatureValidatorIml extends WebServiceGatewaySupport implements SignatureValidator {
...
}
@Component
public class SignatureValidatorJinn extends WebServiceGatewaySupport implements SignatureValidator {
...
}
Spring 使用 JinnServerConfiguration 决定需要注入哪些 bean 实现:
@Configuration
public class JinnServerConfiguration {
final Environment env;
@Autowired
public JinnServerConfiguration(Environment env) {
this.env = env;
}
@Bean
public Jaxb2Marshaller marshaller() {
...
return marshaller;
}
@Bean
public HttpComponentsMessageSender messageSender() {
...
return messageSender;
}
@Bean
@Profile("!local")
public SignatureValidator signatureValidator(Jaxb2Marshaller marshaller,
HttpComponentsMessageSender messageSender) {
SignatureValidatorJinn signatureValidatorJinn = new SignatureValidatorJinn(env);
signatureValidatorJinn.setDefaultUri("http://.../SignatureValidationService");
signatureValidatorJinn.setMarshaller(marshaller);
signatureValidatorJinn.setUnmarshaller(marshaller);
signatureValidatorJinn.setMessageSender(messageSender);
return signatureValidatorJinn;
}
@Bean
@Profile("local")
public SignatureValidator signatureValidatorLocal(){
return new SignatureValidatorIml();
}
}
对于 UserController 中的所有服务,模拟工作正常。
但是当我想模拟 SignatureValidator
时,它是行不通的。
在调试模式下,我看到,在测试中它使用了模拟 bean,但随后 UserController 运行它使用 Spring bean (SignatureValidatorJinn
) 而不是模拟 bean。
为什么使用 SignatureValidatorJinn?我认为是这样,因为测试 class 有注释:@ActiveProfiles(profiles = "embedded")。并根据 JinnServerConfiguration "@Profile("!local")" spring 做出了这个决定。
这个测试工作正常,直到注释 @Component
被添加到 SignatureValidatorIml
class 和 SignatureValidatorJinn
class.
我不明白,为什么它使用 Spring bean。因为其他服务模拟工作正常。我找到了很多关于 mock 的资料,但找不到解决这个问题的方法。
我尝试做下一件事,但他们没有帮助:
- 不要使用 MockitoAnnotations.initMocks(this);
- 为 SignatureValidator 限定当前 bean:Jinn 或 Iml;
- 使用@InjectMocks UserController 用户控制器;
编辑
如果我删除 SignatureValidatorJinn 和 SignatureValidatorIml 中的 @Component 注释,效果会很好。但我不明白为什么会这样。
例如,class 使用“@Service”注释的 UserService。它正在成功地模拟。
那么,也许有人可以解释一下,为什么“@Component”在这种情况下不允许创建模拟对象?
EDIT2
问题已解决
我无法删除注释组件,因为如果没有这个注释,我的应用程序中的 DI 将无法工作。
我找到了决定。 Bean 称为 signatureValidator。我使用名称 SignatureValidator 来指定要模拟的 bean。 那是错误的) 所以按照下面的结构它工作正常:
@MockBean(name = "signatureValidator")
private SignatureValidator signatureValidator;
谢谢大家的回复:)
您尝试模拟 SignatureValidator
bean。但是您拥有并使用了两个 bean SignatureValidatorJinn
和 SignatureValidatorIml
。您的控制器使用这两个 bean。
尝试模拟 SignatureValidatorJinn 和 SignatureValidatorIml 之一。
@MockBean(name = "SignatureValidatorJinn")
private SignatureValidator signatureValidatorJinn;
或
@MockBean(name = "SignatureValidatorIml")
private SignatureValidator signatureValidatorIml;
在您的 post("/distributions/1/receive")
请求中使用。
你不能打电话给
MockitoAnnotations.initMocks(this);
因为 SpringRunner
已经初始化了模拟。通过调用 initMocks
,您将一个新对象分配给 signatureValidator
。 when
和 verify
等所有 Mockito 操作都在新对象上执行,但 Spring 仍然使用 [=11= 创建的 "old" SignatureValidator
].
我从 Spring
和 Mockito
开始工作。
我使用 JUnit
、Mockito
、Spring boot
。
我已经测试了控制器,并尝试让它工作。
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = CommonConfiguration.class)
@ActiveProfiles(profiles = "embedded")
public class UserController_IntegrationWContainer_Tests {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext wac;
@MockBean(name = "SignatureValidator")
private SignatureValidator signatureValidator;
private ObjectMapper objectMapper;
...
@Before
public void before() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).dispatchOptions(true).build();
this.objectMapper = new ObjectMapper();
}
@Test
public void createConfirmation() throws Exception {
...
ConfirmationDetails signature = new ConfirmationDetails("signature");
when(this.signatureValidator.validateAndEnforceSignature(signature))
.thenReturn("advanced");
mockMvc.perform(post("/distributions/1/receive")
.content(this.objectMapper.writeValueAsBytes(signature))
.contentType(MediaType.APPLICATION_JSON)
.andExpect(status().is(201));
}
此测试class用于测试UserController
。
@RestController
@Validated
@Transactional
public class UserController {
private final static Logger logger = LoggerFactory.getLogger(UserController.class);
private UserService userService;
private SignatureValidator signatureValidator;
...
@Autowired
public UserController(UserService userService, SignatureValidator signatureValidator, ...) {
this.userService = userService;
this.signatureValidator = signatureValidator;
...
}
@RequestMapping(
value = "/distributions/{distributionId}/receive",
method = RequestMethod.POST,
produces = "application/json")
public ResponseEntity<Object> confirmReceipt(
@RequestBody ConfirmationDetails signature,
@Context HttpServletRequest request,
@Digits(fraction = 0, integer = 19, message = "ID cannot exceed 19 digits")
@Min(value = 1, message = "ID must be > 0")
@PathVariable("distributionId") Long distributionId) {
logger.info("Processing request confirmReceipt");
/*for this string I want to use stub. but stub doesn't work*/
String advancedSignature = signatureValidator.validateAndEnforceSignature(signature);
...
Map<String, String> resultsMap = new HashMap<>();
resultsMap.put("code", "RECEIPT_SIGNED");
resultsMap.put("message", "Distribution download confirmed");
return new ResponseEntity<>(resultsMap, HttpStatus.CREATED);
}
}
SignatureValidator 接口:
public interface SignatureValidator {
@SuppressWarnings("unchecked")
String validateAndEnforceSignature(ConfirmationDetails signature);
}
SignatureValidator 的两个实现:
@Component
public class SignatureValidatorIml extends WebServiceGatewaySupport implements SignatureValidator {
...
}
@Component
public class SignatureValidatorJinn extends WebServiceGatewaySupport implements SignatureValidator {
...
}
Spring 使用 JinnServerConfiguration 决定需要注入哪些 bean 实现:
@Configuration
public class JinnServerConfiguration {
final Environment env;
@Autowired
public JinnServerConfiguration(Environment env) {
this.env = env;
}
@Bean
public Jaxb2Marshaller marshaller() {
...
return marshaller;
}
@Bean
public HttpComponentsMessageSender messageSender() {
...
return messageSender;
}
@Bean
@Profile("!local")
public SignatureValidator signatureValidator(Jaxb2Marshaller marshaller,
HttpComponentsMessageSender messageSender) {
SignatureValidatorJinn signatureValidatorJinn = new SignatureValidatorJinn(env);
signatureValidatorJinn.setDefaultUri("http://.../SignatureValidationService");
signatureValidatorJinn.setMarshaller(marshaller);
signatureValidatorJinn.setUnmarshaller(marshaller);
signatureValidatorJinn.setMessageSender(messageSender);
return signatureValidatorJinn;
}
@Bean
@Profile("local")
public SignatureValidator signatureValidatorLocal(){
return new SignatureValidatorIml();
}
}
对于 UserController 中的所有服务,模拟工作正常。
但是当我想模拟 SignatureValidator
时,它是行不通的。
在调试模式下,我看到,在测试中它使用了模拟 bean,但随后 UserController 运行它使用 Spring bean (SignatureValidatorJinn
) 而不是模拟 bean。
为什么使用 SignatureValidatorJinn?我认为是这样,因为测试 class 有注释:@ActiveProfiles(profiles = "embedded")。并根据 JinnServerConfiguration "@Profile("!local")" spring 做出了这个决定。
这个测试工作正常,直到注释 @Component
被添加到 SignatureValidatorIml
class 和 SignatureValidatorJinn
class.
我不明白,为什么它使用 Spring bean。因为其他服务模拟工作正常。我找到了很多关于 mock 的资料,但找不到解决这个问题的方法。
我尝试做下一件事,但他们没有帮助:
- 不要使用 MockitoAnnotations.initMocks(this);
- 为 SignatureValidator 限定当前 bean:Jinn 或 Iml;
- 使用@InjectMocks UserController 用户控制器;
编辑
如果我删除 SignatureValidatorJinn 和 SignatureValidatorIml 中的 @Component 注释,效果会很好。但我不明白为什么会这样。
例如,class 使用“@Service”注释的 UserService。它正在成功地模拟。
那么,也许有人可以解释一下,为什么“@Component”在这种情况下不允许创建模拟对象?
EDIT2
问题已解决
我无法删除注释组件,因为如果没有这个注释,我的应用程序中的 DI 将无法工作。 我找到了决定。 Bean 称为 signatureValidator。我使用名称 SignatureValidator 来指定要模拟的 bean。 那是错误的) 所以按照下面的结构它工作正常:
@MockBean(name = "signatureValidator")
private SignatureValidator signatureValidator;
谢谢大家的回复:)
您尝试模拟 SignatureValidator
bean。但是您拥有并使用了两个 bean SignatureValidatorJinn
和 SignatureValidatorIml
。您的控制器使用这两个 bean。
尝试模拟 SignatureValidatorJinn 和 SignatureValidatorIml 之一。
@MockBean(name = "SignatureValidatorJinn")
private SignatureValidator signatureValidatorJinn;
或
@MockBean(name = "SignatureValidatorIml")
private SignatureValidator signatureValidatorIml;
在您的 post("/distributions/1/receive")
请求中使用。
你不能打电话给
MockitoAnnotations.initMocks(this);
因为 SpringRunner
已经初始化了模拟。通过调用 initMocks
,您将一个新对象分配给 signatureValidator
。 when
和 verify
等所有 Mockito 操作都在新对象上执行,但 Spring 仍然使用 [=11= 创建的 "old" SignatureValidator
].