I wanted to test same method from controller and service layer. The question is: Why do I have to use @MockBean annotation in controller, why not @Mock annotation for BookFindOperationsService bookService. Same question for service, why do I need to @Mock repository, why not to use @MockBean? Could you give me difference between these two?
Here is controller:
@RestController
public class BookFindOperationsController {
private final BookFindOperationsService bookService;
@Autowired
public BookFindOperationsController(BookFindOperationsService bookService) {
this.bookService = bookService;
}
@GetMapping("/books/author/{authorID}")
public List<Book> findBooksByAuthor(@PathVariable String authorID) {
return bookService.findBooksByAuthor(authorID);
}
}
Here is service class:
@Service
public class BookFindOperationsService {
private final BookRepository bookRepository;
@Autowired
public BookFindOperationsService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
public List<Book> findBooksByAuthor(String authorID) {
return bookRepository.findByAuthorAllIgnoreCase(authorID);
}
}
Service test:
@RunWith(MockitoJUnitRunner.class)
public class BookFindOperationsServiceTest {
@Mock
BookRepository bookRepository;
@InjectMocks
BookFindOperationsService bookFindOperationsService;
@Test
public void findBooksByAuthor() {
Book book = createDummyBook();
List<Book> books = new ArrayList<>();
books.add(book);
when(bookRepository.findByAuthorAllIgnoreCase("Henryk Sienkiewicz")).thenReturn(books);
assertEquals(1, bookFindOperationsService.findBooksByAuthor("Henryk Sienkiewicz").size());
}
private Book createDummyBook() {
return new Book("W pustyni i w puszczy", "Henryk Sienkiewicz", "dramat", true);
}
Controller test:
@RunWith(SpringRunner.class)
@WebMvcTest(BookFindOperationsController.class)
public class BookFindOperationsControllerTest {
@Autowired
MockMvc mockMvc;
@MockBean
BookFindOperationsService bookService;
@Test
public void findBooksByAuthor() throws Exception {
List<Book> books = new ArrayList<>();
Book book = new Book("W pustyni i w puszczy", "Henryk Sienkiewicz", "dramat", true);
books.add(book);
when(bookService.findBooksByAuthor("Henryk Sienkiewicz")).thenReturn(books);
String expected = "[{\"id\":0,\"title\":\"W pustyni i w puszczy\",\"author\":\"Henryk Sienkiewicz\",\"category\":\"dramat\",\"available\":true}]";
MvcResult mvcResult = mockMvc.perform(get("/books/author/Henryk Sienkiewicz")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andReturn();
String content = mvcResult.getResponse().getContentAsString();
assertEquals(expected, content);
verify(bookService, times(1)).findBooksByAuthor(anyString());
}
The main difference between @Mock and @MockBean is that the former belongs to Mockito framework where as the later belongs to Spring Test Framework under Mockito.
The @MockBean creates/replaces a Spring Bean with a Mocked bean so that other Spring Loaded beans (Controller etc.) can make use of it. That is why you need it when you use MockMvc.perform.
The @Mock annotation doesn't work with Spring Context. It will just try to map a mocked object marked with this annotation to a property of an objected marked with @InjectMocks.
UPDATE
When @WebMvcTest(BookFindOperationsController.class) is used a Spring Context is created which has all the necessary beans to support the running of BookFindOperationsController class as a Web Application. Which means any Interceptors, Filters, Converters also need to be loaded in the Spring Context. This Spring Context will also load a BookFindOperationsService of its own which is not a mock but an actual implementation. But for Testing you need to a Mock that is why you annotate that bean with @MockBean to instruct the Spring Context to use the Mocked one instead of the Actual. This is an integration test because you test whether all components work together correctly.
When you use @RunWith(MockitoJUnitRunner.class) There won't be any Spring Context created implicitly. So you can not use @MockBean because there is no Spring Beans to be mocked. This is a unit test because you are only testing BookFindOperationsService while mocking BookRepository without actually saving anything on Database.
Hope this explains well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With