3

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());
    }
pipilam
  • 587
  • 3
  • 9
  • 22
  • @vmaldosan nah, I want to know why I can not use Mock instead of MockBean. Im getting NullPointer or something. – pipilam Dec 14 '18 at 09:54

2 Answers2

3

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.

shazin
  • 21,379
  • 3
  • 54
  • 71
  • 1
    Yeah, but... Service is also a bean. Why in service test I can not make `@MockBean BookFindOperationsService bookFindOperationsService` ? – pipilam Dec 14 '18 at 09:56
  • Please read the update. – shazin Dec 14 '18 at 10:14
  • It seems that I do not understand why first is integration test and the other is unit test. Why I can not write @RunWith(MockitoJUnitRunner.class) also in `BookFindOperationsController` and conversely why I can not write @WebMvcTest(BookFindOperationsService .class) ? For me, test in controller is also unit test, because I do not save anything on DataBase. – pipilam Dec 14 '18 at 11:25
  • Read the first paragraph in update section slowly. – shazin Dec 14 '18 at 11:35
  • 1
    to be honest, I've read it about 5+ times :D Still cant get it. In my opinion, you've written about what is happening when I use @Webmvc... or `@RunWith(MockitoJUnitRunner.class)` annotation, no why I should use it and which I should use it particular class. For me, these two tests are the same. I test the same method, but from different Components. Really, want to know what exactly is going on here. – pipilam Dec 14 '18 at 11:45
  • Is there any chance to get answer on https://stackoverflow.com/questions/53776659/difference-with-unit-testing-controller-and-service-method/53777238?noredirect=1#comment94408722_53777238 ? – pipilam Dec 14 '18 at 13:00
0

@MockBean is used to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context. You can use it in integration tests

@Mock is used in unit test to replace some implementation. Look here

One important note when using @MockBean. In this cases spring context is not cached and if you have many integration tests initializations of context could take a lot of time.

Community
  • 1
  • 1
marok
  • 1,996
  • 1
  • 14
  • 20
  • Ye, but those two tests are unit test and here I have `@MockBean`. Why I can not use @Mock instead of @MockBean? – pipilam Dec 14 '18 at 09:54
  • And second test work for you without any additional annotations before class? It look like integration test not unit. – marok Dec 14 '18 at 09:58
  • seems like didnt paste annotation. Edited question. Check it out. – pipilam Dec 14 '18 at 09:59
  • @RunWith(SpringRunner.class) means that you start spring context to run this test and @mockBean replace existing bean – marok Dec 14 '18 at 10:01
  • yeah, I know what these annotation do, but it was not a question. I asked why I can not use @Mock instead of @MockBean? Both are unit test. Why for you, controller test is integration test? – pipilam Dec 14 '18 at 10:03
  • For me it's integration test because you start spring context. So you start context and you want to replace dependency which is some bean - you use MockBean. If you use in your test bookFindOperationsController.findBooksByAuthor in this case you can use Mock because spring context is not needed here. – marok Dec 14 '18 at 10:14
  • You are saying that in `BookFindOperationsControllerTest ` I can write Mock instead of MockBean? – pipilam Dec 14 '18 at 10:19