java - Spring boot @MockBean 疏远行为

标签 java spring unit-testing spring-mvc spring-boot

此问题涉及this one 。一旦确定了问题并成功应用了建议的解决方案,我就继续开发和重构我的代码,直到达到这一点。

正如您在下面的代码中看到的,我为在 Controller GetExchangeRate 中实例化的服务类定义了一个 bean,因此我将其直接注入(inject) Controller 并避免注入(inject)其依赖项(ExchangeRateView 是一个存储库,其实现使用 JdbcTemplate)。然后,我重构了我的测试,因此我不需要模拟 ExchangeRateView,而是模拟 GetExchangeRate。但是,执行此操作后,我收到 Application failed to start 错误,提示

Parameter 0 of constructor in com.fx.exchangerate.store.infrastructure.persistence.read.ExchangeRateJdbcView required a bean of type 'org.springframework.jdbc.core.JdbcTemplate'

在我看来,即使我通过 @MockBean 注释模拟 GetExchangeRate,它仍然试图从应用程序上下文中获取其依赖项,因为每当我将 @MockBean ExchangeRateView ExchangeRateView 添加到测试类时它都会正常运行。

所以我的问题是,@MockBean 真的有这样的行为吗?模拟类是否仍然需要注入(inject)其依赖项?我在这里遗漏了什么吗?

Controller :

@RestController
public class ExchangeRateStoreController {
    private AddExchangeRateRequestAdapter addExchangeRateRequestAdapter;
    private GetExchangeRate getExchangeRate;
    private CommandBus commandBus;

    @Autowired
    public ExchangeRateStoreController(CommandBus commandBus, GetExchangeRate getExchangeRate) {
        addExchangeRateRequestAdapter = new AddExchangeRateRequestAdapter();
        this.commandBus = commandBus;
        this.getExchangeRate = getExchangeRate;
    }

    @GetMapping
    public ExchangeRate get(@RequestBody GetExchangeRateRequest getExchangeRateRequest) {
        GetExchangeRateQuery query = new GetExchangeRateQuery(getExchangeRateRequest.from, getExchangeRateRequest.to, getExchangeRateRequest.date);
        return getExchangeRate.execute(query);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void create(@RequestBody AddExchangeRateRequest addExchangeRateRequest) {
        commandBus.dispatch(addExchangeRateRequestAdapter.toCommand(addExchangeRateRequest));
    }
}

测试:

@RunWith(SpringRunner.class)
@WebMvcTest(ExchangeRateStoreController.class)
public class ExchangeRateStoreControllerTest {

    @Autowired
    private MockMvc mvc;
    @MockBean
    ExchangeRateRepository exchangeRateRepository;
    @MockBean
    ExchangeRateDateValidator exchangeRateDateValidator;
    @MockBean
    GetExchangeRate getExchangeRate;

    @Test
    public void givenValidAddExchangeRateRequest_whenExecuted_thenItReturnsAnHttpCreatedResponse() throws Exception {
        String validRequestBody = "{\"from\":\"EUR\",\"to\":\"USD\",\"amount\":1.2345,\"date\":\"2018-11-19\"}";

        doNothing().when(exchangeRateDateValidator).validate(any());
        doNothing().when(exchangeRateRepository).save(any());

        mvc.perform(post("/").content(validRequestBody).contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isCreated());
    }

    @Test
    public void givenGetExchangeRateRequestThatMatchesResults_whenExecuted_thenItReturnsOkResponseWithFoundExchangeRate() throws Exception {
        when(getExchangeRate.execute(any(GetExchangeRateQuery.class))).thenReturn(anExchangeRate());
        mvc.perform(get("/")
                .content("{\"from\":\"EUR\",\"to\":\"USD\",\"date\":\"2018-11-19\"}")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }
}

申请服务:

public class GetExchangeRate {

    private ExchangeRateView view;
    private Clock clock;

    public GetExchangeRate(ExchangeRateView view, Clock clock) {
        this.view = view;
        this.clock = clock;
    }

    // More methods here
}

存储库实现类:

@Repository
public class ExchangeRateJdbcView implements ExchangeRateView {

    JdbcTemplate jdbcTemplate;

    @Autowired
    public ExchangeRateJdbcView(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // More methods here
}

配置类别:

@Configuration
public class ExchangeRateStoreConfig {

    @Bean
    @Autowired
    public GetExchangeRate getExchangeRate(ExchangeRateView exchangeRateView, Clock clock) {
        return new GetExchangeRate(exchangeRateView, clock);
    }

    @Bean
    public Clock clock() {
        return new Clock();
    }

    // More bean definitions
}

最佳答案

我终于找到了这个问题的根本原因。我发现这是由于我将 @ComponentScan(basePackages = {"com.mycompany.myapp.infrastruct", "com.mycompany.myapp.application"} ) 添加到我的 Spring Boot 的主类中,因此 @WebMvcTest 无法正常运行。

你可以在spring boot的文档中找到解释:

If you use a test annotation to test a more specific slice of your application, you should avoid adding configuration settings that are specific to a particular area on the main method’s application class.

The underlying component scan configuration of @SpringBootApplication defines exclude filters that are used to make sure slicing works as expected. If you are using an explicit @ComponentScan directive on your @SpringBootApplication-annotated class, be aware that those filters will be disabled. If you are using slicing, you should define them again.

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html

关于java - Spring boot @MockBean 疏远行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53545831/

相关文章:

unit-testing - 我应该测量 GUI 测试的代码覆盖率吗?

Java循环退出条件

django - 在 Django Forms 中应该测试什么?

java - Android View 添加 child

java - Spring 重试 XML 等效于 @EnableRetry

java - ${catalina.base}/lib 子目录下的属性文件解析

java - 从 Spring Boot 1.3 升级到 1.4 RC1 时 org.hibernate.internal.SessionFactoryImpl.<init> 抛出的 AbstractMethodError

C#单元测试,如何测试大于

java - 在拇指上方显示修改后的 JSlider 值

java - jUnit 测试不输出任何内容