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

Posted

技术标签:

【中文标题】与单元测试控制器和服务方法的区别[重复]【英文标题】:Difference with unit testing controller and service method [duplicate] 【发布时间】:2019-05-15 13:02:49 【问题描述】:

我想从控制器和服务层测试相同的方法。问题是:为什么我必须在控制器中使用@MockBean注解,为什么BookFindOperationsService bookService不用@Mock注解。同样的服务问题,为什么我需要@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());
    

【问题讨论】:

@vmaldosan 不,我想知道为什么我不能使用 Mock 而不是 MockBean。我得到 NullPointer 什么的。 【参考方案1】:

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

@MockBean 使用 Mocked bean 创建/替换 Spring Bean,以便其他 Spring Loaded bean(控制器等)可以使用它。这就是为什么您在使用MockMvc.perform 时需要它。

@Mock 注释不适用于 Spring 上下文。它只会尝试将标有此注释的模拟对象映射到标有@InjectMocks 的对象的属性。

更新

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

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

希望这能很好地解释。

【讨论】:

是的,但是...服务也是一个 bean。为什么在服务测试中我无法制作@MockBean BookFindOperationsService bookFindOperationsService 请阅读更新。 我好像不明白为什么首先是集成测试,然后是单元测试。为什么我也不能在 BookFindOperationsController 中写 @RunWith(MockitoJUnitRunner.class) ,反之为什么我不能写 @WebMvcTest(BookFindOperationsService .class) ?对我来说,控制器中的测试也是单元测试,因为我没有在数据库上保存任何东西。 更新部分第一段慢慢看。 说实话,我已经阅读了大约 5 次以上:D 仍然无法理解。在我看来,您已经写过当我使用 @Webmvc... 或 @RunWith(MockitoJUnitRunner.class) 注释时发生了什么,不知道为什么我应该使用它以及我应该使用哪个特定的类。对我来说,这两个测试是一样的。我测试了相同的方法,但来自不同的组件。真的,想知道这里到底发生了什么。【参考方案2】:

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

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

使用@MockBean 时的一个重要注意事项。在这种情况下,spring 上下文不会被缓存,如果你有很多集成测试,上下文的初始化可能会花费很多时间。

【讨论】:

是的,但这两个测试是单元测试,这里我有@MockBean。为什么我不能使用@Mock 而不是@MockBean? 第二个测试为你工作,上课前没有任何额外的注释?它看起来像集成测试而不是单元。 似乎没有粘贴注释。已编辑的问题。看看吧。 @RunWith(SpringRunner.class) 表示你启动spring上下文来运行这个测试并且@mockBean替换现有的bean 是的,我知道这些注释的作用,但这不是问题。我问为什么我不能使用@Mock 而不是@MockBean?两者都是单元测试。为什么对你来说,控制器测试就是集成测试?

以上是关于与单元测试控制器和服务方法的区别[重复]的主要内容,如果未能解决你的问题,请参考以下文章

单元测试控制器时无法模拟 Grails 服务方法 - MissingMethodException

Spring Boot APP - 单元测试[重复]

在 VS 中运行单元测试与调试单元测试时,我应该期待啥区别?

如何将 @WebMvcTest 用于单元测试 POST 方法?

单元测试中测试用例的设计方法

单元测试基础知识