与单元测试控制器和服务方法的区别

时间:2018-12-14 09:18:09

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

我想从控制器和服务层测试相同的方法。问题是:为什么我必须在控制器中使用@MockBean注释,为什么不为@Mock使用BookFindOperationsService bookService注释。服务方面的问题相同,为什么我需要@Mock存储库,为什么不使用@MockBean?你能给我两者之间的区别吗?

这是控制者:

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

}

这是服务类别:

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

服务测试:

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

控制器测试:

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

2 个答案:

答案 0 :(得分:3)

@Mock@MockBean之间的主要区别在于,前者属于Mockito框架,而后者属于Mockito下的Spring Test Framework。

@MockBean用一个模拟的豆创建/替换一个弹簧豆,以便其他弹簧加载的豆(控制器等)可以使用它。这就是为什么在使用MockMvc.perform时需要它的原因。

@Mock注释不适用于Spring Context。它将仅尝试将标有此批注的模拟对象映射到标有@InjectMocks的对象的属性。

更新

使用@WebMvcTest(BookFindOperationsController.class)时,将创建一个Spring Context,该上下文具有所有必需的bean,以支持将BookFindOperationsController类作为Web应用程序运行。这意味着任何拦截器,过滤器,转换器也需要在Spring Context中加载。这个Spring Context还将加载自己的BookFindOperationsService,它不是模拟的而是实际的实现。但是对于测试,您需要一个Mock,这就是为什么要用@MockBean对该bean进行注释,以指示Spring Context使用Mocked而不是Actual的原因。这是一个集成测试,因为您可以测试所有组件是否可以正常协同工作。

使用@RunWith(MockitoJUnitRunner.class)时,不会隐式创建任何Spring Context。因此,您不能使用@MockBean,因为没有要模拟的Spring Bean。这是一个单元测试,因为您仅在模拟BookFindOperationsService时测试BookRepository,而没有在数据库上实际保存任何内容。

希望这很好地解释了。

答案 1 :(得分:0)

@MockBean用于将模拟对象添加到Spring应用程序上下文中。该模拟将替换应用程序上下文中任何现有的相同类型的bean。您可以在集成测试中使用它

@Mock在单元测试中用于替换某些实现。 看here

使用@MockBean时的一个重要说明。在这种情况下,不会缓存spring上下文,并且如果您有很多集成测试,则上下文的初始化可能会花费很多时间。