【问题标题】: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 什么的。

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


【解决方案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?两者都是单元测试。为什么对你来说,控制器测试就是集成测试?
猜你喜欢
  • 2017-02-19
  • 1970-01-01
  • 2014-10-11
  • 1970-01-01
  • 2017-09-07
  • 2014-01-16
  • 2023-03-30
  • 2012-05-20
  • 1970-01-01
相关资源
最近更新 更多