【问题标题】:Spring boot @MockBean estrange behaviourSpring boot @MockBean 疏远行为
【发布时间】:2019-05-01 22:02:26
【问题描述】:

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

正如您在下面的代码中看到的,我已经为我在控制器GetExchangeRate 中实例化的服务类定义了一个 bean,因此我将它直接注入控制器并避免注入它的依赖项 (ExchangeRateView a其实现使用JdbcTemplate) 的存储库。然后,我重构了我的测试,所以我不需要模拟ExchangeRateView,而是模拟GetExchangeRate。但是,在这样做之后,我收到了 Application failed to start 错误,抱怨说

com.fx.exchangerate.store.infrastructure.persistence.read.ExchangeRateJdbcView 中构造函数的参数 0 需要 'org.springframework.jdbc.core.JdbcTemplate' 类型的 bean

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

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

控制器:

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

【问题讨论】:

  • 你需要模拟一个 JdbcTemplate bean。 bean 构造函数期望提供该类型的 bean,但您的 Test 类没有定义任何。
  • 但是我在模拟类,为什么我必须为模拟提供模拟?这很违反直觉。不是吗?如果我用Mockito.mock(GetExchangeRate.class) 模拟班级,我想我不必提供JdbcTemplate 模拟,对吧?
  • 你说得对:一方面JdbcTemplate 是必需的,因为模拟是通过动态代码生成完成的,使用继承。另一方面,这个 jdbcTemplate 不会在测试中使用。我宁愿实现一个非参数构造函数ExchangeRateJdbcView() 来解决这个问题。

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


【解决方案1】:

我终于找到了这个问题的根本原因。我发现这是因为我将@ComponentScan(basePackages = {"com.mycompany.myapp.infrastructure", "com.mycompany.myapp.application"} ) 添加到了我的spring boot 的主类中,所以@WebMvcTest 没有正常运行。

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

如果您使用测试注释来测试更具体的片段 应用程序,您应该避免添加以下配置设置 特定于 main 方法的应用程序类的特定区域。

@SpringBootApplication 的底层组件扫描配置 定义用于确保切片的排除过滤器 预期的。如果您使用显式的 @ComponentScan 指令 你的 @SpringBootApplication-annotated 类,请注意那些 过滤器将被禁用。如果你使用切片,你应该定义 又来了。

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

【讨论】:

    猜你喜欢
    • 2021-07-28
    • 2017-01-18
    • 1970-01-01
    • 2019-08-10
    • 2017-06-17
    • 2019-09-07
    • 2019-10-10
    • 1970-01-01
    • 2018-07-27
    相关资源
    最近更新 更多