In my unit tests, I need to mock a data-model instance in a spring #Controller, else the return value of the #RequestMapping method gets wrong.
To do this I tried the following:
Create a User mock, which calls user.login() and needs to return "true"
Inject mock object in the LoginController
Stub the login method to return true
Perform POST /Login with MockMVC from spring test
verify that mockUser.login got called
Here is the controller method:
#RequestMapping(value = "/Login", method = RequestMethod.POST)
public String updateUI(Locale locale, Model model, #RequestParam("username") String username,
#RequestParam("hashedPW") String hashedPW, HttpServletRequest request) {
model.addAttribute("username", username);
user = new User(username, username, hashedPW.getBytes(), LoginHandler.getInstance());
boolean loginResult = user.login();
if(loginResult == true) {
return "profile";
}
String output = "Failed login (" + username + ") requested, locale = " + locale;
log(output);
return "home";
}
and my initialization of mock objects with injection:
#Mock
private User mockUser;
#InjectMocks
private LoginController injectedLoginController;
#Before
public void setup() throws ServletException {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(injectedLoginController).build();
LOGFILE = new File("logs/general.log");
}
and finally the unit test:
#Test
public void testLoginSuccess() throws Exception {
String username = "Stefan";
byte[] hashedPW = "".getBytes();
when(mockUser.login()).thenReturn(true);
ResultActions ra = mvc
.perform(post("/Login").param("username", username).param("hashedPW", hashedPW.toString()))
.andExpect(status().isOk());
verify(mockUser).login();
}
All together I expected the User object that is handled by the controller to be of mockUser type instead of User, and the login() method to get called once (accordingly) and return "true".
But all I get is
"Wanted but not invoked: mockUser.login()
Actually, there was zero interaction with this mock."
I appreciate any suggestions to solve my problem since I am working on this for quite some time now and I don't seem to get the trick.
1) This seems to be an IT test.. you should not force any injection.. spring does that:
#InjectMocks
private LoginController injectedLoginController;
2) You need to spy on your controller:
#SpyBean
private LoginController injectedLoginController;
3) You need to move the creation of the User to a package level method inside the controller:
user = createUser(username, username, hashedPW.getBytes(), LoginHandler.getInstance());
...
User createUser(...){
return new User(username, username, hashedPW.getBytes(), LoginHandler.getInstance());
}
4) Make that method return a mocked User:
doReturn(mockUser).when(injectedLoginController).createUser(...);
when(mockUser.login()).thenReturn(true);
If you don't want to change your code, you can take help from PowerMockito which can help to mock construction of new objects.
#RunWith(PowerMockRunner.class)
#PrepareForTest({User.class})
public class ControllerUnderTest {
// here User is class for which we want to mock object creation
}
Now, let's mock object
#Test
public void testLoginSuccess() throws Exception {
....
User userMock = PowerMockito.createMock(User.class);
PowerMockito.whenNew(User.class).withArguments(username, username, hashedPW.getBytes(), LoginHandler.getInstance()).thenReturn(userMock);
expect(userMock.login()).andReturn(true);
verify(mockUser).login();
...
}
For more details on powermock, please check this PowerMockito
Related
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.
I am using Spring framework 2.4.4 and Intellij Ultimate 2020
I would like to test this method tryGetUser on which I am passing HttpSession, but I can't resolve the problem, because when I am mocking it and setAttribute(); at the //Arrange part of the method it always ends with currentUserUsername to be null.
public User tryGetUser(HttpSession session) {
String currentUserUsername = (String) session.getAttribute("currentUserUsername");
***// Here this local variable currentUsername is always null;***
if (currentUserUsername == null) {
throw new AuthorizationException("No logged in user.");
}
try {
return userService.getByUsername(currentUserUsername);
} catch (EntityNotFoundException e) {
throw new AuthorizationException("No logged in user.");
}
}
Here you can see how I am trying to Mock it, but actually, the session is remaining empty or I don't know, but when the service method starts to execute the attribute is not there.
#ExtendWith(MockitoExtension.class)
public class LoginServiceMvcTests {
#Mock
UserRepository mockUserRepository;
#Mock
UserService mockUserService;
#InjectMocks
MockHttpSession mockHttpSession;
#InjectMocks
LoginServiceMvc userService;
private User junkie;
private User organizer;
#BeforeEach
public void setup() {
junkie = Helpers.createJunkie();
organizer = Helpers.createOrganizer();
}
#Test
public void tryGetUser_Should_GetUserFromSession_When_UserIsLogged() {
// Arrange
mockHttpSession.setAttribute("luboslav", junkie);
Mockito.when(mockUserService.getByUsername("luboslav"))
.thenReturn(junkie);
// Act
userService.tryGetUser(mockHttpSession);
// Assert
Mockito.verify(mockUserService, Mockito.times(1))
.getByUsername("luboslav");
}
}
From Jon skeet answer Mockito doesn't work that way, so you need to actually mock the call instead of setting attribute
Mockito.when(mockHttpSession. getAttribute("currentUserUsername"))
.thenReturn("name");
And also HttpSession should be annotated with #Mock not #InjectMocks
#Mock
HttpSession httpSession;
Use MockHttpSession, which is an actual real object (not just a Mockito interface mock) that is intended for exactly this type of testing. It's basically an empty/blank container, so you don't have to mock out all the standard behavior, just fill it with whatever attributes you want.
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
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);