Reputation: 8387
As I am new to Spring Test MVC I don't understand this problem. I took the code below from http://markchensblog.blogspot.in/search/label/Spring
Variable mockproductService
is not injected from Application Context and it contains null
values while using @Mock
annotation and getting assetion error.
The Assertion error I currently encounter is as follows:
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)
Note: If I use @Autowired
instead of @Mock
it is working fine.
Test Controller class
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 class
@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>
Upvotes: 3
Views: 23784
Reputation: 566
I think there's a problem with the answer that @Cebence provided in that it doesnt take into account the OP's usage of spring-webmvc-test @WebApplication. If you were you run the example provided with
@RunWith(MockitoJUnitRunner.class)
and you still have your
@Autowired private WebApplicationContext wac;
Then the test will fail. I was experiencing the same problem as @Human Being and I found an easy solution was to set the service within the controller as not required. Not ideal but here's the solution:
The controller:
@Controller
public class MyController
{
@Autowired(required=false)
MyService myService;
.
.
.
}
The Test:
@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"));
}
}
and application context:
<?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>
Upvotes: 3
Reputation: 2416
I haven't looked at the tutorial you mentioned because the code you provided says enough on expertize, or lack of it, of the original author.
General rule of testing is that you don't mix different types of tests. First level of testing are unit tests which mean that you are testing a single unit of work (usually a single class). Once the unit tests pass you write integration tests that combine certain components (classes) and test how they work together.
A class rarely depends on nothing so to create a real good unit test you need to mock all its dependencies.
@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);
}
}
That is how a unit test should look like. First you prepare the data (objects) - notice they are all mocked. Also, using final
prevents accidental assignments, i.e. to overwrite existing value by accident.
Second, you configure every mocked object's behavior. If a Product
will be asked for ID then you specify what the mocked instance will return in that case. BTW I really don't see the purpose of setting those product IDs so the first part of the test could look like this:
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);
Third, call the method under test and store its result:
final String returned = productController.testController(mmap);
And finally you check if the class under test behaved as expected. In this case ModelMap
's addAttribute()
method should have been called with those exact parameter values. And returned string should not be null
, and should be "show_products"
- note the parameter order of assertEquals(expected, actual)
method because, in case of failed test, JUnit will print out a message saying "Expected THIS but got THAT.".
Mockito.verify(mmap).addAttribute("Products", products);
assertNotNull(returned);
assertEquals("show_products", returned);
Good luck testing!
P.S. I forgot to explain the beginning:
@RunWith(MockitoJUnitRunner.class)
public class ProductControllerTest {
@Mock private ProductService mockproductService;
@InjectMocks private ProductController productController;
In order for the @InjectMocks
to work like Spring's @Autowired
the test must be ran with MockitoJUnitRunner
class - it will locate all @Mock
members, create them and inject the right ones into the member marked with @InjectMocks
.
Upvotes: 3
Reputation: 2124
For me it's unclear how the combination of Spring and Mockito as you took it from the referenced blog source should work at all as expected. At least I may explain your observation:
this.mockMvc.perform()
) is working on the web application context created by Spring. In that context ProductController
was instantiated by Spring (context:component-scan
). The productService
was then autowired with the Mockito mock you created in mvc-dispatcher-servlet.xml
as someDependencyMock
.mockproductService
via @Autowired
, Spring injects the someDependencyMock
instance from its context. So your Mockito.when()
call works correctly on this instance, which was already correctly wired to the ProductController
as mentioned before.mockproductService
via @Mock
, Mockito injects a new instance of ProductService
, not the one of the Spring context, since it knows nothing about Spring at all. So your Mockito.when()
call does not operate on the mock which was autowired by Spring and thus someDependencyMock
stays uninitialized.So what's left unclear for me about how the original code from the blog worked at all is:
productController
property annotated with @InjectMocks
will be initialized by Mockito and even correctly wired to the mockproductService
in the test class. But Spring does not know anything about that object and won't use it in this.mockMvc.perform()
calls. So I assume if you inject mockproductService
only with @Autowired
your test works as intended even if you delete both the productController
property and the MockitoAnnotations.initMocks()
call in your test class.Upvotes: 8