由于我是Spring Test MVC的新手,我不明白这个问题。我从http://markchensblog.blogspot.in/search/label/Spring
中获取了以下代码变量mockproductService
未从Application Context注入,并且在使用null
注释并获得断言错误时包含@Mock
值。
我目前遇到的断言错误如下:
java.lang.AssertionError: Model attribute 'Products' expected:<[com.pointel.spring.test.Product@e1b42, com.pointel.spring.test.Product@e1f03]> but was:<[]>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:60)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:89)
at org.springframework.test.web.servlet.result.ModelResultMatchers$2.match(ModelResultMatchers.java:68)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:141)
at com.pointel.spring.test.ProductControllerTest.testMethod(ProductControllerTest.java:84)
注意:如果我使用@Autowired
代替@Mock
,则表示正常。
测试控制器类
RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations={"classpath:mvc-dispatcher-servlet.xml"})
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class})
public class ProductControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@InjectMocks
private ProductController productController;
@Mock
//@Autowired
private ProductService mockproductService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
List<Product> products = new ArrayList<Product>();
Product product1 = new Product();
product1.setId(new Long(1));
Product product2 = new Product();
product2.setId(new Long(2));
products.add(product1);
products.add(product2);
Mockito.when(mockproductService.findAllProducts()).thenReturn(products);
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
}
@Test
public void testMethod() throws Exception {
List<Product> products = new ArrayList<Product>();
Product product1 = new Product();
product1.setId(new Long(1));
Product product2 = new Product();
product2.setId(new Long(2));
products.add(product1);
products.add(product2);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/products");
this.mockMvc.perform(requestBuilder).
andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.model().attribute("Products", products))
//.andExpect(MockMvcResultMatchers.model().size(2))
.andExpect(MockMvcResultMatchers.view().name("show_products"));
}
}
控制器类
@Controller
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping("/products")
public String testController(ModelMap model){
model.addAttribute("Products",productService.findAllProducts());
return "show_products";
}
}
WebServletContext mvc-dispatcher-servlet.xml
<bean id="someDependencyMock" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="com.pointel.spring.test.ProductService" />
</bean>
<context:component-scan base-package="com.pointel.spring.test" />
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" >
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
答案 0 :(得分:7)
对于我来说,目前还不清楚Spring和Mockito的组合如何从引用的博客源中获取它应该如预期的那样工作。至少我可以解释你的观察:
this.mockMvc.perform()
)正在处理由Spring创建的Web应用程序上下文。在该上下文中,ProductController
由Spring(context:component-scan
)实例化。然后productService
使用您在mvc-dispatcher-servlet.xml
中创建的Mockito模拟作为someDependencyMock
进行自动装配。mockproductService
注入@Autowired
, Spring 会从其上下文中注入someDependencyMock
实例。因此,您的Mockito.when()
调用在此实例上正常运行,该实例已正确连接到前面提到的ProductController
。mockproductService
注入@Mock
, Mockito 会注入新 ProductService
的实例,而不是Spring语境,因为它根本不了解Spring。因此,Mockito.when()
调用不会对Spring自动装配的模拟进行操作,因此someDependencyMock
保持未初始化状态。所以我不清楚博客的原始代码是如何运作的:
productController
注释的@InjectMocks
属性将由Mockito初始化,甚至正确连接到测试类中的mockproductService
。但Spring对该对象一无所知,也不会在this.mockMvc.perform()
调用中使用它。因此,我假设即使您在测试类中同时删除mockproductService
属性和@Autowired
调用,只有productController
注入MockitoAnnotations.initMocks()
您的测试才能正常工作。答案 1 :(得分:2)
我没有看过你提到的教程,因为你提供的代码足以说明原作者的专业化或缺乏专业知识。
测试的一般规则是您不要混合使用不同类型的测试。第一级测试是单元测试,这意味着您正在测试单个工作单元(通常是单个类)。一旦单元测试通过,您可以编写集成测试,它们将某些组件(类)组合在一起并测试它们如何协同工作。
一个类很少依赖于任何东西,所以要创建一个真正好的单元测试,你需要模拟它的所有依赖。
@RunWith(MockitoJUnitRunner.class)
public class ProductControllerTest {
@Mock private ProductService mockproductService;
@InjectMocks private ProductController productController;
@Test
public void testMethod() throws Exception {
// Prepare sample test data.
final Product product1 = Mockito.mock(Product.class);
final Product product2 = Mockito.mock(Product.class);
final ArrayList<Product> products = new ArrayList<Product>();
products.add(product1);
products.add(product2);
final ModelMap mmap = Mockito.mock(ModelMap.class);
// Configure the mocked objects.
Mockito.when(product1.getId()).thenReturn(new Long(1));
Mockito.when(product2.getId()).thenReturn(new Long(2));
Mockito.when(mockproductService.findAllProducts()).thenReturn(products);
final mmap = Mockito.mock(ModelMap.class);
// Call the method under test.
final String returned = productController.testController(mmap);
// Check if everything went as planned.
Mockito.verify(mmap).addAttribute("Products", products);
assertNotNull(returned);
assertEquals("show_products", returned);
}
}
这就是单元测试的样子。首先准备数据(对象) - 注意它们都是嘲笑的。此外,使用final
可防止意外分配,即意外覆盖现有值。
其次,配置每个模拟对象的行为。如果要求Product
提供ID,那么您可以指定在这种情况下被模拟的实例将返回的内容。顺便说一句,我真的没有看到设置这些产品ID的目的,所以测试的第一部分看起来像这样:
final Product product1 = Mockito.mock(Product.class);
final Product product2 = Mockito.mock(Product.class);
final ArrayList<Product> products = new ArrayList<Product>();
products.add(product1);
products.add(product2);
final mmap = Mockito.mock(ModelMap.class);
// Configure the mocked objects.
Mockito.when(mockproductService.findAllProducts()).thenReturn(products);
final mmap = Mockito.mock(ModelMap.class);
第三,调用测试中的方法并存储其结果:
final String returned = productController.testController(mmap);
最后,检查被测试的课程是否符合预期。在这种情况下,应使用这些确切的参数值调用ModelMap
的{{1}}方法。返回的字符串不应该是addAttribute()
,而应该是null
- 请注意"show_products"
方法的参数顺序,因为如果测试失败,JUnit会打印出一条消息“预期的这个但得到了。“。
assertEquals(expected, actual)
祝你好运测试!
P.S。 我忘记解释开头了:
Mockito.verify(mmap).addAttribute("Products", products);
assertNotNull(returned);
assertEquals("show_products", returned);
为了使 @RunWith(MockitoJUnitRunner.class)
public class ProductControllerTest {
@Mock private ProductService mockproductService;
@InjectMocks private ProductController productController;
像Spring @InjectMocks
一样工作,测试必须与@Autowired
类一起运行 - 它将找到所有MockitoJUnitRunner
成员,创建它们并注入正确的成员标记为@Mock
。
答案 2 :(得分:2)
我认为@Cebence提供的答案存在问题,因为它没有考虑OP对spring-webmvc-test @WebApplication的使用。如果您是运行
提供的示例@RunWith(MockitoJUnitRunner.class)
你还有你的
@Autowired private WebApplicationContext wac;
然后测试将失败。我遇到了与@Human Being相同的问题,我发现一个简单的解决方案是在控制器内设置不需要的服务。不理想,但这是解决方案:
控制器:
@Controller
public class MyController
{
@Autowired(required=false)
MyService myService;
.
.
.
}
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:/META-INF/spring/applicationContext-test.xml")
@WebAppConfiguration
public class MyControllerTest
{
// This is the backend service we are going to mock
@Mock
MyService myService;
// This is the component we are testing and we inject our mocked
// objects into it
@InjectMocks
@Resource
private MyController myController;
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setup()
{
MockitoAnnotations.initMocks(this);
this.mockMvc = webAppContextSetup(this.wac).build();
List<Object> data = new ArrayList<Object>();
// Mock one of the service mthods
when(myService.getAll()).thenReturn(datasets);
}
@Test
public void testQuery() throws Exception
{
this.mockMvc.perform(get("/data").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$.value").value("Hello"));
}
}
和应用程序上下文:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/neo4j
http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
">
<mvc:annotation-driven/>
<context:annotation-config/>
<context:component-scan base-package="com.me.controller" />
</beans>