【发布时间】: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