与单元测试控制器和服务方法的区别[重复]
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
在 VS 中运行单元测试与调试单元测试时,我应该期待啥区别?