测试使用 bean 而不是模拟对象

Test used bean instead mock object

我从 SpringMockito 开始工作。 我使用 JUnitMockitoSpring 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 的资料,但找不到解决这个问题的方法。

我尝试做下一件事,但他们没有帮助:

编辑

如果我删除 SignatureValidatorJinn 和 SignatureValidatorIml 中的 @Component 注释,效果会很好。但我不明白为什么会这样。

例如,class 使用“@Service”注释的 UserService。它正在成功地模拟。

那么,也许有人可以解释一下,为什么“@Component”在这种情况下不允许创建模拟对象?

EDIT2

问题已解决

我无法删除注释组件,因为如果没有这个注释,我的应用程序中的 DI 将无法工作。 我找到了决定。 Bean 称为 signatureValidator。我使用名称 SignatureValidator 来指定要模拟的 bean。 那是错误的) 所以按照下面的结构它工作正常:

@MockBean(name = "signatureValidator") 
private SignatureValidator signatureValidator;

谢谢大家的回复:)

您尝试模拟 SignatureValidator bean。但是您拥有并使用了两个 bean SignatureValidatorJinnSignatureValidatorIml。您的控制器使用这两个 bean。 尝试模拟 SignatureValidatorJinn 和 SignatureValidatorIml 之一。

@MockBean(name = "SignatureValidatorJinn")
private SignatureValidator signatureValidatorJinn;

@MockBean(name = "SignatureValidatorIml")
private SignatureValidator signatureValidatorIml;

在您的 post("/distributions/1/receive") 请求中使用。

你不能打电话给

MockitoAnnotations.initMocks(this);

因为 SpringRunner 已经初始化了模拟。通过调用 initMocks,您将一个新对象分配给 signatureValidatorwhenverify 等所有 Mockito 操作都在新对象上执行,但 Spring 仍然使用 [=11= 创建的 "old" SignatureValidator ].