将 mock 注入 Spring MockMvc WebApplicationContext
Posted
技术标签:
【中文标题】将 mock 注入 Spring MockMvc WebApplicationContext【英文标题】:Inject mock into Spring MockMvc WebApplicationContext 【发布时间】:2015-10-27 10:53:40 【问题描述】:我正在使用 Spring-boot 测试(通过 JUnit4 和 Spring MockMvc)一个 REST 服务适配器。适配器只是将向它发出的请求传递给另一个 REST 服务(使用自定义 RestTemplate
)并将附加数据附加到响应中。
我想运行 MockMvc
测试来执行控制器集成测试,但想用模拟覆盖控制器中的 RestTemplate
以允许我预定义第 3 方 REST 响应并防止它在每次测试。我已经能够通过实例化MockMvcBuilders.standAloneSetup()
并将其传递给控制器以使用post 中列出的注入的模拟进行测试(以及下面的设置)来完成此操作,但是我无法使用MockMvcBuilders.webAppContextSetup()
.
我浏览过其他几篇文章,但没有一篇回答关于如何实现这一点的问题。我想在测试中使用实际的 Spring 应用程序上下文而不是独立的,以防止在应用程序可能增长时出现任何空白。
编辑:我使用 Mockito 作为我的模拟框架,并试图将其中一个模拟注入到上下文中。如果这不是必需的,那就更好了。
控制器:
@RestController
@RequestMapping(Constants.REQUEST_MAPPING_PATH)
public class Controller
@Autowired
private DataProvider dp;
@Autowired
private RestTemplate template;
@RequestMapping(value = Constants.REQUEST_MAPPING_RESOURCE, method = RequestMethod.GET)
public Response getResponse(
@RequestParam(required = true) String data,
@RequestParam(required = false, defaultValue = "80") String minScore
) throws Exception
Response resp = new Response();
// Set the request params from the client request
Map<String, String> parameters = new HashMap<String, String>();
parameters.put(Constants.PARAM_DATA, data);
parameters.put(Constants.PARAM_FORMAT, Constants.PARAMS_FORMAT.JSON);
resp = template.getForObject(Constants.RESTDATAPROVIDER_URL, Response.class, parameters);
if(resp.getError() == null)
resp.filterScoreLessThan(new BigDecimal(minScore));
new DataHandler(dp).populateData(resp.getData());
return resp;
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = MainSpringBootAdapter.class)
@TestPropertySource("/application-junit.properties")
public class WacControllerTest
private static String controllerURL = Constants.REQUEST_MAPPING_PATH + Constants.REQUEST_MAPPING_RESOURCE + compressedParams_all;
private static String compressedParams_all = "?data=data&minScore=minScore";
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@InjectMocks
private Controller Controller;
@Mock
private RestTemplate rt;
@Value("$file")
private String file;
@Spy
private DataProvider dp;
@Before
public void setup() throws Exception
dp = new DataProvider(file);
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
@Test
public void testGetResponse() throws Exception
String[] strings = "requestData", "100";
Mockito.when(
rt.getForObject(Mockito.<String> any(), Mockito.<Class<Object>> any(), Mockito.<Map<String, ?>> any()))
.thenReturn(populateTestResponse());
mockMvc.perform(get(controllerURL, strings)
.accept(Constants.APPLICATION_JSON_UTF8))
.andDo(MockMvcResultHandlers.print());
Mockito.verify(rt, Mockito.times(1)).getForObject(Mockito.<String> any(), Mockito.<Class<?>> any(), Mockito.<Map<String, ?>> any());
private Response populateTestResponse()
Response resp = new Response();
resp.setScore(new BigDecimal(100));
resp.setData("Some Data");
return resp;
【问题讨论】:
【参考方案1】:Spring 的MockRestServiceServer
正是您要找的。
类的javadoc的简短描述:
客户端 REST 测试的主要入口点。用于涉及直接或间接(通过客户端代码)使用 RestTemplate 的测试。提供了一种对将通过 RestTemplate 执行的请求设置细粒度期望的方法,以及一种定义要发回的响应的方法,从而无需实际运行的服务器。
尝试像这样设置您的测试:
@WebAppConfiguration
@ContextConfiguration(classes = YourSpringConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class ExampleResourceTest
private MockMvc mockMvc;
private MockRestServiceServer mockRestServiceServer;
@Autowired
private WebApplicationContext wac;
@Autowired
private RestOperations restOperations;
@Before
public void setUp() throws Exception
mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
mockRestServiceServer = MockRestServiceServer.createServer((RestTemplate) restOperations);
@Test
public void testMyApiCall() throws Exception
// Following line verifies that our code behind /api/my/endpoint made a REST PUT
// with expected parameters to remote service successfully
expectRestCallSuccess();
this.mockMvc.perform(MockMvcRequestBuilders.get("/api/my/endpoint"))
.andExpect(status().isOk());
private void expectRestCallSuccess()
mockRestServiceServer.expect(
requestTo("http://remote.rest.service/api/resource"))
.andExpect(method(PUT))
.andRespond(withSuccess("\"message\": \"hello\"", APPLICATION_JSON));
【讨论】:
在看到其他帖子后,我实际上已经尝试过使用它,但没有任何运气。在经历了很多挫折之后,我终于今天能够让它工作,并且没有意识到包含覆盖我的RestTemplate
bean 的其他 @Configuration
也覆盖了 MockRestServiceServer
注入的 bean。我将提出另一种方法来说明如何在没有MockRestServiceServer
的情况下完成同样的事情。非常感谢。【参考方案2】:
这是另一个解决方案。简单地说,它只是创建一个新的RestTemplate
bean 并覆盖已经注册的那个。
因此,虽然它执行的功能与 @mzc 答案相同,但它允许我使用 Mockito 更轻松地制作响应和验证匹配器。
这不仅仅是几行代码,但它也避免了必须添加额外的代码来将 Response
对象转换为上述 mockRestServiceServer.expect().andRespond(<String>)
方法的 arg 的字符串。
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringApplicationConfiguration(classes = MainSpringBootAdapter.class)
@TestPropertySource("/application-junit.properties")
public class WacControllerTest
private static String Controller_URL = Constants.REQUEST_MAPPING_PATH + Constants.REQUEST_MAPPING_RESOURCE + compressedParams_all;
@Configuration
static class Config
@Bean
@Primary
public RestTemplate restTemplateMock()
return Mockito.mock(RestTemplate.class);
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@InjectMocks
private Controller Controller;
@Mock
private RestTemplate rt;
@Value("$file")
private String file;
@Spy
private DataProvider dp;
@Before
public void setup() throws Exception
dp = new DataProvider(file);
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
this.rt = (RestTemplate) this.wac.getBean("restTemplateMock");
@Test
public void testGetResponse() throws Exception
String[] strings = "request", "100";
//Set the request params from the client request
Map<String, String> parameters = new HashMap<String, String>();
parameters.put(Constants.PARAM_SINGLELINE, strings[0]);
parameters.put(Constants.PARAM_FORMAT, Constants.PARAMS_FORMAT.JSON);
Mockito.when(
rt.getForObject(Mockito.<String> any(), Mockito.<Class<Object>> any(), Mockito.<Map<String, ?>> any()))
.thenReturn(populateTestResponse());
mockMvc.perform(get(Controller_URL, strings)
.accept(Constants.APPLICATION_JSON_UTF8))
.andDo(MockMvcResultHandlers.print());
Mockito.verify(rt, Mockito.times(1)).getForObject(Mockito.<String> any(), Mockito.<Class<?>> any(), Mockito.<Map<String, ?>> any());
private Response populateTestResponse()
Response resp = new Response();
resp.setScore(new BigDecimal(100));
resp.setData("Some Data");
return resp;
【讨论】:
我向不应该被命名的对手发誓(!),春天充满了谜团。这就像一个魅力。我几乎要开始实施配置文件了,还有什么不是的,但它做到了!谢谢。我的问题:为什么会这样?我尝试在另一个 \@Configuration 类中创建一个新的 \@Bean,但 Spring 抱怨有重复的 bean 和所有内容。是什么让它起作用? @apil.tamang@Configuration
是 bean 的 java 配置。它与 xml 配置同义。在这种情况下,我们正在连接另一个 RestTemplate
bean,但返回它的模拟实例。此外,@Primary
“表示当多个候选者有资格自动装配单值依赖项时,应优先考虑一个 bean”
好的。在我尝试过之前只是一个快速的。如果没有@Primary 注解,这个方案可以工作吗?除了您已经说过的以外,@Primary 有什么特别之处?
@apil.tamang 如果我没记错的话,将使用 Spring 的产品 RestTemplate
。不是你需要的模拟。您可能需要稍后查看@BeanMock
,以确保在测试之外扫描应用程序组件时不会发现此新 bean
@ethesx 谢谢,你让我开心。我认为生活中有比spring创建和组合测试上下文的方式更简单的事情:)【参考方案3】:
org.springframework.boot.test.mock.mockito.MockBean@MockBean 帮了我。
【讨论】:
以上是关于将 mock 注入 Spring MockMvc WebApplicationContext的主要内容,如果未能解决你的问题,请参考以下文章
spring boot 测试无法注入 TestRestTemplate 和 MockMvc
Spring Boot MVC 测试 - MockMvc 始终为空
@CurrentSecurityContext 总是在 mockMvc 集成测试中注入 null?
Spring4 Spring MVC实战——MockMvc报org.springframework.core.CollectionFactory.createLinkedMap错误