英文:
Testing REST services with Spring MVC and mocking services
问题
我有一个Spring MVC应用程序。我想测试这个控制器:
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {
"classpath:testDatabaseContext.xml"
})
public class TimeControllerTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Mock
private AutorisationService autorisationService;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void should_OK() throws Exception {
doReturn(User.builder().build()).when(autorisationService).findById(any(Date.class),anyLong(), 1L);
mockMvc.perform(get("/time/2")
.contentType(APPLICATION_JSON))
.andExpect(status().isOk());
}
}
但是当我运行测试时,我得到了这个错误:
org.mockito.exceptions.misusing.NullInsteadOfMockException:
传递给 when() 的参数是 null!
正确存根示例:
doThrow(new RuntimeException()).when(mock).someMethod();
另外,如果你使用了 @Mock 注解,不要忘记 openMocks()
英文:
I have a Spring MVC application . I want to test this controller:
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {
"classpath:testDatabaseContext.xml"
})
public class TimeControllerTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Mock
private AutorisationService autorisationService;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void should_OK() throws Exception {
doReturn(User.builder().build()).when(autorisationService).findById(any(Date.class),anyLong(), 1L);
mockMvc.perform(get("/time/2")
.contentType(APPLICATION_JSON))
.andExpect(status().isOk());
}
}
but when I run the test I have this error:
org.mockito.exceptions.misusing.NullInsteadOfMockException:
Argument passed to when() is null!
Example of correct stubbing:
doThrow(new RuntimeException()).when(mock).someMethod();
Also, if you use @Mock annotation don't miss openMocks()
答案1
得分: 1
我看到有几个原因导致这不起作用:
A - 仅有@Mock是不足以初始化一个模拟对象的。你需要:
- 在setup中调用MockitoAnnotations.initMocks()
- 移除@Mock,并在setup中调用Mockito.mock(UserRepository.class)
然而,我认为这还不足够,这让我想到:
B - 你的控制器似乎由Spring管理,而你模拟的AutorisationService却没有被管理。
我在这里假设你的控制器使用了一个由Spring注入的AutorisationService。
在这里你有几个选项:
- 选项1 - 在测试中完全不使用Spring,使用Mockito来管理你的控制器和服务。
应该类似于:
@InjectMocks
private SomeController someController;
@Mock
private AutorisationService autorisationService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(someController).build();
}
注意standaloneSetup方法:这里的想法是你不使用Spring上下文(顶部的注解可以移除)。
- 选项2:使用没有Spring Boot的Spring,使用一个模拟的服务。
这有点像一个技巧,但我经常使用它。
你需要提供一个自定义的Spring上下文,其中包含一个由Mockito提供的AutorisationService类型的bean。
// 添加所有其他必要的bean
// 在你的上下下文中可以使用你需要的任何内容:@ComponentScan("somepackage")、@Import(Config1.class, Config2.class)、@ImportResource("context.xml")、@Bean方法...
@Configuration
class SpringWithMockitoConfig {
@Bean
AutorisationService autorisationService() {
return Mockito.mock(AutorisationService.class);
}
}
然后你的测试将有两个约束:
- 导入自定义配置:@ContextConfiguration(classes = SpringWithMockitoConfig.class)
- 在每个测试的setup方法中重置模拟对象:Mockito.reset(autorisationService);
使用这个解决方案,你不需要真实的AutorisationService所依赖的那些bean(我假设是Dao、DataSource等)。
基本上只需要mvc部分。
- 选项3:使用Spring Boot和@MockBean注解 - 这是我目前不熟悉的。
英文:
I see several reasons why this does not work :
A - @Mock alone is not enough to initialize a mock. You need either :
- Call MockitoAnnotations.initMocks() in setup
- Remove @Mock and call Mockito.mock(UserRepository.class) in setup
However I don't think this will be enough, which brings me to :
B - Your controller looks managed by spring, whereas your mocked AutorisationService is not.
I am assuming there that your controller uses an AutorisationService supposedly injected by spring.
You have several options here :
- Option 1 - Don't use spring at all for your test, and manage your controller and service with mockito.
This should look like :
@InjectMocks
private SomeController someController;
@Mock
private AutorisationService autorisationService;
@Before
public void setup() {
MockitoAnnotations.initMocks();
this.mockMvc = MockMvcBuilders.standaloneSetup(someController).build();
}
Note the standaloneSetup method : the idea here is that you are not using a spring context (annotations at the top can then be removed).
- Option 2 : Use spring without springboot, with a mocked service.
Kind of a hack, but I use it on regular basis.
You have to provide a custom spring context, with a bean of type AutorisationService, but provided by mockito.
// All all other beans necessary
// You can use whatever you need in your context : @ComponentScan("somepackage"), @Import(Config1.class, Config2.class), @ImportResource("context.xml"), @Bean methods ...
@Configuration
class SpringWithMockitoConfig {
@Bean
AutorisationService autorisationService() {
return Mockito.mock(AutorisationService.class);
}
}
You will then have two constraint on your tests :
- Import the custom config : @ContextConfiguration(classes = SpringWithMockitoConfig.class)
- Reset the mocks on every test in the setup method : Mockito.reset(autorisationService);
With this solution you don't need the beans your real AutorisationService relies on (I assume Dao's, DataSource and such).
Basicly only the mvc part.
- Option 3 : use SpringBoot and the @MockBean annotation - which I am not familiar with yet
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论