after fixing a bug in an older Java Spring MVC 4.1 application, I wanted to add a unit test, but the method the current code base is using for testing won't actually execute validation.
So I wanted to add MVCMock, but when it executes the validation methods, the values passed to isValid is always null.
Relevant files below (I've tried to strip out as much noise as possible):
// Unit Test
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#EnableWebMvc
#ContextConfiguration(locations = {"/applicationContext-test.xml"})
public class ExampleControllerTest extends AbstractControllerTestBase {
#Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
#Before
public void setup() {
this.mockMvc = webAppContextSetup(this.context).build();
}
#Test
public void fileUploadZipArchive() throws Exception {
// Upload a zip file
File mockFile = new File("src/test/resources/fixtures/ex.zip");
MockHttpServletRequestBuilder multipart = MockMvcRequestBuilders
.fileUpload("/files/ex/upload/Tex")
.file("ex.zip", FileUtils.readFileToByteArray(mockFile));
MvcResult result = mockMvc.perform(multipart)
.andReturn();
}
// Bean
public class FileUploadBean {
#Valid
#MultipartMimeTypeMatch
private MultipartFile file = null;
// ...
}
// Validator
public class MultipartMimeTypeMatchValidator implements ConstraintValidator<MultipartMimeTypeMatch, Object> {
// ...
public boolean isValid(Object value, final ConstraintValidatorContext context) {
// value and context is always null
}
}
// Controller
#RequestMapping(value = "/files/{ex1}/upload/{ex2}", method = RequestMethod.POST)
public Object uploadFile(HttpServletRequest request, #PathVariable String ex1,
#PathVariable String ex2, #Valid FileUploadBean fileUploadBean, BindingResult result) throws IllegalStateException, IOException {
// ...
}
}
What could be going wrong?
NOTE: Spring 4.1
The javadoc of the file method states that the name should be the name of the file. I agree that that is a bit misleading. Instead it should be the name of the request parameter to use, which should be (generally speaking) the same as the property in your model object.
.file("ex.zip", FileUtils.readFileToByteArray(mockFile));
With this a request parameter named ex.zip will be part of the request, however you have one that is named file.
.file("file", FileUtils.readFileToByteArray(mockFile));
Using the above line should fix it and properly bind to your object in turn properly invoking your validator.
On a side node, your validator should properly handle the null case as well or add a #NotNull on the field as well. The #Valid on the field doesn't do anything so you can remove that.
Related
I am trying to unit test a Spring MVC controller method, but my unit test keeps failing.
I have a Spring MVC controller method:
// MyController.java
#RequestMapping(value = "/end-point", method = RequestMethod.GET)
public ModelAndView getData(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
ModelAndView mv = new ModelAndView();
DataManager dataManager = DataManager.getInstance();
DataUser currentUser = (DataUser) request.getSession().getAttribute("currentUser");
List<DataItem> dataList = dataManager.getDataForUser(currentUser.getId());
mv.addObject("dataList", dataList);
mv.setViewName("home-page");
return mv;
}
I am trying to test this controller method with JUnit. I have very little experience unit testing and am trying to learn. Seems like this is near-impossible or does not make sense to do without a mocking library, and the project I'm working on already has Mockito as a dependency so I'm trying to use that. My test class is below:
//MyControllerTest.java
public class MyControllerTest {
#InjectMocks
private MyController myController;
#Mock
HttpServletRequest request;
#Mock
HttpServletResponse response;
#Mock
ModelAndView mockModelAndView;
#Mock
DataManager mockDataManager;
#Mock
DataUser mockDataUser;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void getDataTest() throws Exception {
//I guess I have to somehow mock mockDataUser here even more than #Mock???
Mockito.when(request.getSession().getAttribute("currentUser")).thenReturn(mockVendorUser); // <-- this is where the null pointer exception is coming from
Mockito.when(myController.getData(request, response)).thenReturn(mockModelAndView);
ModelAndView testing = profileControllerWH.getMySkus(request, response);
assertEquals(1, 1);
}
}
When I run my test, it fails and I get a java.lang.NullPointerException exception in the console, specifying the line above with the null pointer exception comment.
I have tried looking up how to mock classes with Mockito, and I keep seeing the #Mock annotation, which I already have as #Mock DataUser (as well as other classes I am using in the controller method that I guess I need to mock).
How do I get this to work? It seems like I have to create a whole new DataUser object with fake data, but then that seems to defeat the purpose of the mocking library. What would the point of using Mockito be if I have to create my own objects with fake data? Or, I might be misunderstanding this because of lack of experience.
Remember that by default, unstubbed methods return default value of return type when called (0 for numbers, null for Objects).
You haven't stubbed request.getSession() so it returns null.
You need to:
provide a mock for session
stub request.getSession() to return this mock
stub session.getAttribute("currentUser")
On top of that:
While calling the controller method in your test certainly has value of testing the method body, but you will test more functionality (like request and response serialization) if you re-implement your test as a #WebMvcTest
I have a Spring #RestController that has a field of Apache Camel interface FluentProducerTemplate.
I am testing the controller with MockMvc and I am injecting FluentProducerTemplate as a mock.
I would like to mock only one method - request(), and use the real implementation of the other methods.
However, I get NullPointerException from the unmocked methods. Other FluentProducerTemplate methods n , their return type is FluentProducerTemplate. In the implementation they return this. The mock object returns null.
I thought that mockito #Mock mocks only the methods that I specify. Other methods use the original implementation. Is this a correct statement?
I tried #Spy instead of #Mock and I got the same error.
When I mock all the methods that I work with then it works and no NullPointerException.
Code:
REST Controller:
#RestController
#RequestMapping("/v1/test”)
public class MyController {
#EndpointInject(uri = "direct:main")
private FluentProducerTemplate producerTemplate;
#RequestMapping(value = “/test2”, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public MyResponse testRequest(
#RequestHeader(“id”) String id,
#RequestHeader(“context”) String context,
#RequestBody RequestBody requestBody
) {
MyResponse response = producerTemplate
.withHeader(“id”, id)
.withHeader(“context”, context)
.withBody(requestBody)
.request(MyResponse.class);
return response;
}
Test:
#RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {
private MockMvc mockMvc;
#Mock
private FluentProducerTemplate producerTemplateMock;
#InjectMocks
private MyControllerTest myController;
private static MyResponse expectedResultSuccess;
private static String requestString;
private static HttpHeaders allRequestHeaders;
#BeforeClass
public static void setup() {
allRequestHeaders = new HttpHeaders();
allRequestHeaders.set(“id”, “123”);
allRequestHeaders.set(“context”, “ABCD1234”);
allRequestHeaders.set(“Content-Type”, “application/json”);
expectedResultSuccess = new MyResponse(“test”);
requestString = “request”BodyText;
}
#Before
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(myController).build();
when(producerTemplateMock.request(any())).thenReturn(expectedResultSuccess);
}
#Test
public void testSuccess() throws Exception {
mockMvc.perform(post(“/v1/test/test2)
.headers(allRequestHeaders)
.content(requestString))
.andExpect(status().isOk())
}
}
The test pass only when I add the below to init():
when(producerTemplateMock.withHeader(any(), any())).thenReturn(producerTemplateMock);
when(producerTemplateMock.withBody(any())).thenReturn(producerTemplateMock);
My main question is - why do I have to mock all methods?
I prefer to use the original implementation of withHeader() and withBody() and mock only request().
You want so called partial mocks. Depending on whether you want to set up mostly mocks or mostly call real implementations there are different prefered approaches.
1. spy for few mocks, mostly real implementation
If you want to mock only some methods and otherwise call the real implementation:
FluentProducerTemplate producerTemplateMock = spy(FluentProducerTemplate.class);
// Mock implementation
doReturn(expectedResultSuccess).when(producerTemplateMock).request(any());
// All other method call will use the real implementations
2. mock for mostly mocks, few real implementations
FluentProducerTemplate producerTemplateMock = mock(FluentProducerTemplate.class);
// Mock methods
when(producerTemplateMock.request(any())).thenReturn(expectedResultSuccess);
// tell mockito to call the real methods
when(producerTemplateMock.withHeader(any(), any())).thenCallRealMethod;
when(producerTemplateMock.withBody(any())).thenCallRealMethod();
As you can see, the 2nd approach is more boilerplate to write. However, it depends on your use case what approach is better suited.
First of all, I have the following endpoint method present within a class called RecipeController:
#RequestMapping(value = {"/", "/recipes"})
public String listRecipes(Model model, Principal principal){
List<Recipe> recipes;
User user = (User)((UsernamePasswordAuthenticationToken)principal).getPrincipal();
User actualUser = userService.findByUsername(user.getUsername());
if(!model.containsAttribute("recipes")){
recipes = recipeService.findAll();
model.addAttribute("nullAndNonNullUserFavoriteRecipeList",
UtilityMethods.nullAndNonNullUserFavoriteRecipeList(recipes, actualUser.getFavoritedRecipes()));
model.addAttribute("recipes", recipes);
}
if(!model.containsAttribute("recipe")){
model.addAttribute("recipe", new Recipe());
}
model.addAttribute("categories", Category.values());
model.addAttribute("username", user.getUsername());
return "recipe/index";
}
As you can see above, the method takes as a second parameter a Principal object. When running the application, the parameter points to a non-null object as expected. It contains information about the user that is currently logged in within the application.
I have created a test class for the RecipeController called RecipeControllerTest. This class contains a single method called testListRecipes.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebAppConfiguration
public class RecipeControllerTest{
#Mock
private RecipeService recipeService;
#Mock
private IngredientService ingredientService;
#Mock
private StepService stepService;
#Mock
private UserService userService;
#Mock
private UsernamePasswordAuthenticationToken principal;
private RecipeController recipeController;
private MockMvc mockMvc;
#Before
public void setUp(){
MockitoAnnotations.initMocks(this);
recipeController = new RecipeController(recipeService,
ingredientService, stepService, userService);
mockMvc = MockMvcBuilders.standaloneSetup(recipeController).build();
}
#Test
public void testListRecipes() throws Exception {
User user = new User();
List<Recipe> recipes = new ArrayList<>();
Recipe recipe = new Recipe();
recipes.add(recipe);
when(principal.getPrincipal()).thenReturn(user);
when(userService.findByUsername(anyString()))
.thenReturn(user);
when(recipeService.findAll()).thenReturn(recipes);
mockMvc.perform(get("/recipes"))
.andExpect(status().isOk())
.andExpect(view().name("recipe/index"))
.andExpect(model().attributeExists("recipes"))
.andExpect(model().attributeExists("recipe"))
.andExpect(model().attributeExists("categories"))
.andExpect(model().attributeExists("username"));
verify(userService, times(1)).findByUsername(anyString());
verify(recipeService, times(1)).findAll();
}
}
As you can see in this second snippet, I tried to mock the Principal object within the test class, using the UsernamePasswordAuthenticationToken implementation.
When I run the test, I get a NullPointerException, and the stacktrace points me to the following line from the first snippet of code:
User user = (User)((UsernamePasswordAuthenticationToken)principal).getPrincipal();
The principal object passed as a parameter to the listRecipes method from is still null, even though I tried to provide a mock object.
Any suggestions ?
Create a class that implements Principal:
class PrincipalImpl implements Principal {
#Override
public String getName() {
return "XXXXXXX";
}
}
Sample test:
#Test
public void login() throws Exception {
Principal principal = new PrincipalImpl();
mockMvc.perform(get("/login").principal(principal)).andExpect(.........;
}
Spring MVC is very flexible with controller arguments, which lets you put most of the responsibility of looking up information onto the framework and focus on writing the business code. In this particular case, while you can use Principal as a method parameter, it's usually much better to use your actual principal class:
public String listRecipes(Model model, #AuthenticationPrincipal User user)
To actually set the user for a test, you need to work with Spring Security, which means adding .apply(springSecurity()) to your setup. (Complications like this, by the way, are the main reason I dislike using standaloneSetup, as it requires you to remember to duplicate your exact production setup. I recommend writing actual unit tests and/or full-stack tests.) Then annotate your test with #WithUserDetails and specify the username of the test user.
Finally, as a side note this controller pattern can be simplified significantly with Querydsl, as Spring is able to inject a Predicate that combines all of the filter attributes you're looking up by hand, and then you can pass that predicate to a Spring Data repository.
Did you try using...?
#Test
#WithMockUser(username = "my_principal")
public void testListRecipes() {
...
I've started playing around with mockito today and I've encountered a problem. This is the class I'm trying to create test cases on:
#Path("search")
public class SearchWebService {
private static final Logger logger = Logger.getLogger(SearchWebService.class);
#EJB
UserServiceInterface userService;
#GET
#Path("/json/{searchstring}")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
#RolesAllowed("User")
public List getJSONFromSearchResults(#PathParam("searchstring") String searchString, #Context HttpServletRequest request) {
logger.info("getJSONFromSearchResults called");
//Users own email
String ownemail = request.getRemoteUser();
if (searchString.contains(" ")) {
//Split String in two at first space
String names[] = searchString.split("\\s+", 2);
List userList = userService.searchByFullName(names[0], names[1], ownemail);
if (userList.size() > 0) {
return userList;
} //Check for cases where the last name contains spaces
else {
return userService.searchByLastName(searchString, ownemail);
}
}
return userService.searchBySingleName(searchString, ownemail);
}
}
I'm at searchString.contains(" ") and I'm trying to invoke "when(...).thenReturn(...)" But mockito throws an exception saying that "Cannot mock/spy class java.lang.String" I'm not sure I'm doing it correctly when testing this web service. Maybe there is some otherway to do this? Here is my test class:
public class SearchWebServiceTest {
#Mock
UserServiceInterface mockedUserService;
#Mock
Logger mockedLogger;
#Mock
HttpServletRequest mockedRequest;
#Mock
String mockedString;
#Mock
List<SearchResultsContainer> mockedUserList;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void testGetJSONFromSearchResultsSpace() throws Exception {
when(mockedRequest.getRemoteUser()).thenReturn("email");
when("StringWithSpace".contains(" ")).thenReturn(true);
when("StringWitchSpace".split("\\s+", 2)).thenReturn(null);
when(mockedUserService.searchByFullName("name1", "name2", "email")).thenReturn(mockedUserList);
assertTrue(mockedUserList.size() > 0);
}
you cannot mock final classes (like String). This is a known limitation of the framework.
You can refer this link.
Mockito Verification Not Failing
I hope it helps !!!
If you need to invoke your service with a String that has a space, then just pass it a string that has a space. And don't mock the class that you are trying to test. You should mock as little as possible in unit tests. Simply provide real input data that meets the particular conditions of your particular test. Only mock collaborators, and only when you need to. If you need a String (either as a parameter or as a return value of a collaborator) that meets certain conditions, then just provide such an example String.
so if you need to test some method on string then best way is use reflection method to assign value to your string variable.
I used apache common library for it
FieldUtils.writeField(yourClass, "variableName", "value", true);
With Spring MVC, you can specify that a particular URL will handled by a particular method, and you can specify that particular parameters will map to particular arguments, like so:
#Controller
public class ImageController {
#RequestMapping("/getImage")
public String getImage( #RequestParam("imageId") int imageId, Map<String,Object> model ) {
model.put("image",ImageService.getImage(imageId));
}
}
This is all well and good, but now I want to test that an http request with an imageId parameter will invoke this method correctly. In other words, I want a test that will break if I remove or change any of the annotations. Is there a way to do this?
It is easy to test that getImage works correctly. I could just create an ImageController and invoke getImage with appropriate arguments. However, this is only one half of the test. The other half of the test must be whether getImage() will be invoked by the Spring framework when an appropriate HTTP request comes in. I feel like I also need a test for this part, especially as my #RequestMapping annotations become more complex and invoke complex parameter conditions.
Could you show me a test that will break if I remove line 4, #RequestMapping("getImage")?
You could use AnnotationMethodHandlerAdapter and its handle method programmatically. This will resolve the method for the given request and execute it. Unfortunately this is a little indirect. Actually there is a private class called ServletHandlerMethodResolver in AMHA that is responsible for just resolving the method for a given request. I just filed a request for improvement on that topic, as I really would like to see this possible, too.
In the meantime you could use e.g. EasyMock to create a mock of your controller class, expect the given method to be invoked and hand that mock to handle.
Controller:
#Controller
public class MyController {
#RequestMapping("/users")
public void foo(HttpServletResponse response) {
// your controller code
}
}
Test:
public class RequestMappingTest {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private MyController controller;
private AnnotationMethodHandlerAdapter adapter;
#Before
public void setUp() {
controller = EasyMock.createNiceMock(MyController.class);
adapter = new AnnotationMethodHandlerAdapter();
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
}
#Test
public void testname() throws Exception {
request.setRequestURI("/users");
controller.foo(response);
EasyMock.expectLastCall().once();
EasyMock.replay(controller);
adapter.handle(request, response, controller);
EasyMock.verify(controller);
}
}
Regards,
Ollie
Ollie's solution covers testing the specific example of an annotation but what about the wider question of how to test all the other various Spring MVC annotations. My approach (that can be easily extended to other annotations) would be
import static org.springframework.test.web.ModelAndViewAssert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({/* include live config here
e.g. "file:web/WEB-INF/application-context.xml",
"file:web/WEB-INF/dispatcher-servlet.xml" */})
public class MyControllerIntegrationTest {
#Inject
private ApplicationContext applicationContext;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private HandlerAdapter handlerAdapter;
private MyController controller;
#Before
public void setUp() {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
handlerAdapter = applicationContext.getBean(HandlerAdapter.class);
// I could get the controller from the context here
controller = new MyController();
}
#Test
public void testFoo() throws Exception {
request.setRequestURI("/users");
final ModelAndView mav = handlerAdapter.handle(request, response,
controller);
assertViewName(mav, null);
assertAndReturnModelAttributeOfType(mav, "image", Image.class);
}
}
I've also written a blog entry about integration testing Spring MVC annotations.