简体   繁体   中英

Spring boot @MockBean estrange behaviour

This question relates to this one . Once identified the problem, and successfully applied the proposed solution, I've continued developing and refactoring my code, until I reach to this point.

As you can see in the following code, I've defined a bean for the service class I was instantiating in my controller GetExchangeRate , so I inject it directly into the controller and avoid injecting its dependencies ( ExchangeRateView a repository whose implementation makes use of JdbcTemplate ). Then, I've refactored my test, so I don't need to mock ExchangeRateView but GetExchangeRate instead. However, after doing that I'm getting an Application failed to start error, complaining that

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

It seems to me that, even though I'm mocking GetExchangeRate through @MockBean annotation, it's still trying to get its dependencies from the application context, as it runs properly whenever I add @MockBean ExchangeRateView exchangeRateView to the test class.

So my question is, does @MockBean really behaves this way? Does the mocked class still need its dependencies to be injected? Am I missing something here?

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));
    }
}

TEST:

@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());
    }
}

APPLICATION SERVICE:

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 IMPLEMENTATION CLASS:

@Repository
public class ExchangeRateJdbcView implements ExchangeRateView {

    JdbcTemplate jdbcTemplate;

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

    // More methods here
}

CONFIGURATION CLASS:

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

I finally managed to find the root cause of this issue. I turned out that it was due to the fact that I had @ComponentScan(basePackages = {"com.mycompany.myapp.infrastructure", "com.mycompany.myapp.application"} ) added to my spring boot's main class, so @WebMvcTest was not running properly.

You can find the explanation in spring boot's documentation:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM