Spring Mvc unit testing with Mockito
This guide walks you through the process of testing a simple web application FormController and RestController.
Spring framework is more popular because of its IoC container and AOP framework. Similarly Junit is very popular framework to test the unit of code.But consider a scenario where your service code is dependent on some external service which are really works in the enterprise servers.and if you want to test your front end controllers or services without actually deploying your application into enterprise servers. This kind of testing is called integration testing.
Create dummy services
One approach is to test this kind of applications writing our own dummy services which return some dummy data. Mockito is framework which helps to achive this by creating proxy services to us which allows us to test end-to-end functionality of spring based web applications. spring-boot-starter-test dependency comes with Mockito and JUnit framework. Create Simple web application with registration form controller.
Download source code from the github repository below or use below command
$/>git clone https://github.com/go2andplay/spring-junit-mockito.git
in pom.xml testing dependency is having scope value as 'test', that means all these testing related jars test classes not available in the final build. this is the best practice to make your final build clean and small in size. |
Above code is developed using spring boot framework that contains :
Simple RegistrationController with validations enabled Thymeleaf based registration form Validation Messages are rendered from the message.properties (i18n support enabled) WebMvcConfig.java : Java based configuration of MessageSource and Validator You can import this source code into your favorate IDE and run as spring boot application. and point your browser to
http://localhost:8080/
Write your first unit test using Mockito
Write a unit test class under /src/test/java
.
I have created few *Test.java classes. java class RegistrationControllerTest.java
Since we are using spring IoC container following annotation.
You must understand
-
@RunWith(SpringJUnit4ClassRunner.class) : This creates required spring based environment for us
-
@ContextConfiguration(classes=WebMvcConfig.class) : Provide Java based configuration classes where our spring configuration is declared
-
@WebAppConfiguration : As we are using WebMVC test, this provides mock WebApplicationContext and required classes like requestHandlers, HttpServletRequest etc. If you are testing specific Controller or Service layer.this annotation is not required.I have used all above annotations in WebConfigRegistrationControllerTest.Java
With the above configuration testing environment is ready and next we will enable Mockito
Setting up Mockito
Enabling Mockito is possible with two ways:
-
Using @RunWith(MockitoJUnitRunner.class) : since we are already using SpringJUnit4ClassRunner.class we can’t use
-
Using MockitoAnnotations.initMocks(this) (Recommended): calling this code on @Before or @BeforeClass annotated method.
If you are testing Mvc App you need MockMvc need to be enabled.
This can be possible two ways.
Based on the documentation, There are two ways to configure MockMvc:-
-
MockMvcBuilders.webAppContextSetup(webApplicationContext).build() :This approach will automatically load the Spring configuration and inject WebApplicationContext into the test.This is not recommended. please check my test case (WebConfigRegistrationControllerTest.Java) which is failed to inject MockService
package com.trvajjala.test;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.trvajjala.Application;
import com.trvajjala.form.Registration;
import com.trvajjala.service.RegistrationService;
/**
* If you use MockMvcBuilders.webAppContextSetup(this.applicationContext).build(). it try to create complete webAppContext using SpringJunitRunner and
* Configuration classes that you provide
*
* http://docs.spring.io/spring-security/site/docs/current/reference/html/test-mockmvc.html This is not recommended approach as you can see it creates entire
* webAppMockContext but doesn't work mockServices. and test case fails.
*
* @author ThirupathiReddy V
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class)
@WebAppConfiguration
public class WebConfigRegistrationControllerTest {
@Autowired
private WebApplicationContext applicationContext;
private MockMvc mockMvc;
@Mock
RegistrationService registrationService;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Mockito.doNothing().when(this.registrationService).save(Mockito.any(Registration.class));
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.applicationContext).build();
}
@Test
public void invalidLoginFormTest() throws Exception {
//@formatter:off
this.mockMvc
.perform(MockMvcRequestBuilders
.post("/")
.param("username", "xx")
.param("fullname", "xx") //error-1
.param("email","xx") //error-2
)
.andDo(MockMvcResultHandlers.print())
//without passing country code //error-3
.andExpect(MockMvcResultMatchers.model().errorCount(3))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.view().name("index"));
//@formatter:on
}
@Test
public void validLoginFormTest() throws Exception {
// Mocking method call and saying that do nothing when save method is called on registrationService
//@formatter:off
this.mockMvc
.perform(MockMvcRequestBuilders
.post("/")
.param("username", "xx")
.param("fullname", "xx")
.param("email","xx@gmail.com")
.param("country","IN")
)
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.status().isOk())
//check fullname attribute available for for the view result
.andExpect(MockMvcResultMatchers.model().attribute("fullname", "xx"))
.andExpect(MockMvcResultMatchers.view().name("result"));
//@formatter:on
}
}
-
MockMvcBuilders.standaloneSetup(controller).build() :This approach does not load the Spring configuration. only loads specific controller.If you use choose this approach you don’t need to use the above three annotations.please refer RegistrationControllerTest.java and MockitoRunnerRegistrationControllerTest.java
package com.trvajjala.test;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import com.trvajjala.controller.RegistrationController;
import com.trvajjala.service.RegistrationService;
@RunWith(MockitoJUnitRunner.class)
public class MockitoRunnerRegistrationControllerTest {
@InjectMocks
private RegistrationController registrationController;
@Mock
RegistrationService registrationService;
private MockMvc mockMvc;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.standaloneSetup(this.registrationController).build();
}
@Test
public void invalidLoginFormTest() throws Exception {
//@formatter:off
this.mockMvc
.perform(MockMvcRequestBuilders
.post("/")
.param("username", "xxx")
.param("fullname", "xxx") //error-1
.param("email","invalidEmailID") //error-2
)
//without passing country code //error-3
.andExpect(MockMvcResultMatchers.model().errorCount(3))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.view().name("index"));
//@formatter:on
}
@Test
public void validLoginFormTest() throws Exception {
//@formatter:off
this.mockMvc
.perform(MockMvcRequestBuilders
.post("/")
.param("username", "xxx")
.param("fullname", "xxx")
.param("email","xx@gmail.com")
.param("country","IN")
)
.andExpect(MockMvcResultMatchers.model().hasNoErrors())
.andExpect(MockMvcResultMatchers.status().isOk())
//check fullname attribute available for for the view result
.andExpect(MockMvcResultMatchers.model().attribute("fullname", "xxx"))
.andExpect(MockMvcResultMatchers.view().name("result"));
//@formatter:on
}
}
Mock objects are created two ways :
-
Using Mockito class static methods. Mockito.mock(MyService.class) and Mockito.spy(new MyServiceImpl())).check WithoutAnnotationMockTest.java
-
Using annotations .below section describes about these annotations.(@Mock and @Spy)
@Mock vs @InjectMocks Vs @Spy
-
@Mock: creates a mock or proxy object. (use this on anyService that your controller is auto-wiring any services)
-
@InjectMocks: creates an instance of the class and injects the mocks that are created with the @Mock (or @Spy) annotations into this instance.
Don’t use @InjectMocks on interfaces if you do so will get below error message: Cannot instantiate @InjectMocks field named 'registrationService'. You haven’t provided the instance at field declaration so I tried to construct the instance. However, I failed because: the type 'RegistrationService' is an interface |
-
@Spy: If you want to use real object you need to use @Spy annotation. what this means is when you use @Spy you need to create object with new Keyword actual implementation of the real object will be called. where as @Mock will creates dummy implementation. which doesn’t have actual logic. we need to write Mockito.when().then() method which acts accordingly. Refer RegistrationControllerTest.java how we used @Spy
package com.trvajjala.test;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import com.trvajjala.form.Registration;
import com.trvajjala.service.RegistrationService;
/**
* This is simple mockito service call test. but this won't work for mvc Controller testing.
*
* @author ThirupathiReddy V
*
*/
public class WithoutAnnotationMockTest {
// @Mock
private RegistrationService registrationService;
@Before
public void setUp() {
// MockitoAnnotations.initMocks(this);
// registrationService = Mockito.mock(RegistrationService.class);
registrationService = Mockito.spy(RegistrationService.class);
}
@Test
public void mockServiceTest() throws Exception {
Mockito.doNothing().when(registrationService).save(Mockito.any(Registration.class));
registrationService.save(new Registration());
}
}
-
The most widely used annotation in Mockito is @Mock. We can use @Mock to create and inject mocked instances without having to call Mockito.mock manually.
You must use @RunWith(MockitoJUnitRunner.class) or Mockito.initMocks(this) to initialise these mocks and inject them. |
Comments
Post a Comment