2

Could someone tell me how should I test those two methods:

public boolean deleteUser(Principal principal) {
    if (findLoggedInUser(principal) != null) {
        userRepository.delete(findLoggedInUser(principal));
        return true;
    }
    return false;
}

public User findLoggedInUser(Principal principal) {
    return findUserbyUsername(principal.getName());
}

The problem is that I'm using currently logged in user with basic authentication and don't know how, and if I can mock those Principals. Is there a way to do that? Those methods are in my Service layer so maybe I can't do unit tests and I'm left with integration tests because those methods heavily use DB?

EDIT 1: My changed test class:

public class UserServiceBeanTest {

@Spy
@InjectMocks
private UserServiceBean userServiceBean;

@Mock
private UserRepository userRepository;

@Mock
private Principal principal;

@Mock
private PasswordEncoder passwordEncoder;

@Mock
private User userStub;

private String defaultName = "user";
private  String defaultPassword = "password";
private String defaultEmail = "example@example.com";

@Before
public void init() {
    MockitoAnnotations.initMocks(this);
}


@Test
public void shouldReturnTrue_whenUserDeleted() {
    //given
    when(principal.getName()).thenReturn(defaultName);
    when(userServiceBean.findLoggedInUser(principal)).thenReturn(userStub);

    // when
    boolean removed = userServiceBean.deleteUser(principal);

    //then
    assertTrue(removed);
    verify(userRepository, times(1));
}

@Test
public void shouldReturnFalse_whenUserNotFound() {
    //given
    when(principal.getName()).thenReturn(defaultName);
    when(userServiceBean.findLoggedInUser(principal)).thenReturn(null);

    //when
    boolean removed = userServiceBean.deleteUser(principal);

    //then
    assertFalse(removed);
    verify(userRepository, times(0));
}

}

I'm getting those errors now:

org.mockito.exceptions.misusing.UnfinishedVerificationException: 

Missin

g method call for verify(mock) here:
-> at com.doublemc.services.UserServiceBeanTest.shouldReturnTrue_whenUserDeleted(UserServiceBeanTest.java:63)

Example of correct verification:
    verify(mock).doSomething()

Also, this error might show up because you verify either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.


    at com.doublemc.services.UserServiceBeanTest.init(UserServiceBeanTest.java:48)

EDIT 2: Here is my UserServiceBean class:

package com.doublemc.services;

import com.doublemc.domain.ToDoItem;
import com.doublemc.domain.User;
import com.doublemc.repositories.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.security.Principal;

@Service
@Transactional
public class UserServiceBean {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;

@Autowired
UserServiceBean(UserRepository userRepository, PasswordEncoder passwordEncoder) {
    this.userRepository = userRepository;
    this.passwordEncoder = passwordEncoder;
}

public User saveUser(User user) {
    User newUser = new User();
    newUser.setUsername(user.getUsername());
    newUser.setEmail(user.getEmail());
    newUser.setPassword(passwordEncoder.encode(user.getPassword()));
    return userRepository.save(newUser);
}

public boolean userExists(User user) {
    return userRepository.findByUsername(user.getUsername()) != null;
}

public Iterable<ToDoItem> getAllToDoItems(User user) {
    return user.getToDoItems();
}

public boolean deleteUser(Principal principal) {
    if (findLoggedInUser(principal) != null) {
        userRepository.delete(findLoggedInUser(principal));
        return true;
    }
    return false;
}

public User findLoggedInUser(Principal principal) {
    return userRepository.findByUsername(principal.getName());
}

}

Here is my UserRepository:

public interface UserRepository extends CrudRepository<User, Long> {
User findByUsername(String username);
}

EDIT 6: I created myself three more tests:

@Test
public void shouldReturnUser_whenPassedUser() {
    // given
    when(userRepository.save(any(User.class))).thenReturn(new User(defaultName, defaultPassword, defaultEmail));

    // when
    User savedUser = userServiceBean.saveUser(userStub);

    // then
    assertNotNull(savedUser);
    verify(userRepository, times(1)).save(any(User.class));
}

@Test
public void shouldReturnTrue_whenUserExists() {
    // given
    when(userStub.getUsername()).thenReturn(defaultName);
    when(userRepository.findByUsername(userStub.getUsername())).thenReturn(userStub);

    // when
    boolean exists = userServiceBean.userExists(userStub);

    // then
    assertTrue(exists);
    verify(userRepository, times(1)).findByUsername(defaultName);
}

@Test
public void shouldReturnFalse_whenUserNotFoundByUsername() {
    // given
    when(userStub.getUsername()).thenReturn(defaultName);
    when(userRepository.findByUsername(userStub.getUsername())).thenReturn(null);

    // when
    boolean exists = userServiceBean.userExists(userStub);

    // then
    assertFalse(exists);
    verify(userRepository, times(1)).findByUsername(defaultName);
}

And here are tested methods: UserServiceBean.saveUser:

public User saveUser(User user) {
    User newUser = new User(user.getUsername(), user.getEmail(), passwordEncoder.encode(user.getPassword()));
    return userRepository.save(newUser);
}

UserServiceBean.userExists:

public boolean userExists(User user) {
    return userRepository.findByUsername(user.getUsername()) != null;
}
Maciej Kowalski
  • 25,605
  • 12
  • 54
  • 63
doublemc
  • 3,021
  • 5
  • 34
  • 61
  • 1
    Test what about them? – Andy Turner Feb 09 '17 at 22:42
  • Well, if for example deleteUser method actually deletes a user - that's what tests are for, right? – doublemc Feb 09 '17 at 22:43
  • 2
    No, you shouldn't test that `deleteUser` actually deletes a user: you should check that it invoked `userRepository.delete` with the parameter you expect. Whether that actually deletes a user is up to the implementation of `userRepository`. – Andy Turner Feb 09 '17 at 22:45
  • So if I have already tested repositories then I don't have to test any other methods in my Service layer which communicate controllers and repos? – doublemc Feb 09 '17 at 22:52
  • What is `Principal`? There are several types named that. – chrylis -cautiouslyoptimistic- Feb 09 '17 at 23:36
  • Oh sorry, it's currently logged in user in Spring Securty. Here is more about it: http://stackoverflow.com/questions/37499307/whats-the-principal-in-spring-security – doublemc Feb 09 '17 at 23:40

1 Answers1

4

This is how i would do it (Junit + Mockito).

There are two tests cases in the given example.

Btw.. i think you could do a little refactoring as you are (i guess) hitting the database twice:

public boolean deleteUser(Principal principal) {
    User loggedUser = findLoggedInUser(principal);
    if (loggedUser != null) {
        userRepository.delete(loggedUser);
        return true;
    }
    return false;
}

To the tests..

import static org.mockito.Mockito.*;
import mypkg.Service;
import mypkg.User;
import mypkg.UserRepository;
import org.junit.Assert;
import org.junit.Before;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

import java.security.Principal;

public class ServiceTest {

    @Spy
    @InjectMocks
    private Service service;

    @Mock
    private UserRepository userRepository;

    @Mock
    private Principal principal;

    @Mock
    private User userStub;

    private String defaultName = "name";

    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
    }

   @org.junit.Test
   public void shouldReturnTrue_whenUserDeleted() throws Exception{
       // Arrange
       when(principal.getName()).thenReturn(defaultName);
       when(service.findUserbyUsername(defaultName)).thenReturn(userStub);

       // Act
       boolean removed = service.deleteUser(principal);

       // Assert
       Assert.assertTrue(removed);
       verify(userRepository, times(1)).delete(userStub);
   }

    @org.junit.Test
    public void shouldReturnFalse_whenUserNotFound() throws Exception{
        // Arrange
        when(principal.getName()).thenReturn(defaultName);
        when(service.findUserbyUsername(defaultName)).thenReturn(null);

        // Act
        boolean removed = service.deleteUser(principal);

        // Assert
        Assert.assertFalse(removed);
        verify(userRepository, times(0)).delete(userStub);
    }
}

The biggest thing to get out of this is that you Mock / Stub any outside dependencies (UserRepository in this case) and focus only on the logic contained in that service method. What is going inside that delete is not relevant to the test.. all you care about is whether that method has been invoked with a certain parameter.. and thats it.

Let me know if all is clear.. i will explain if needed.

Update

@InjectMocks is a convenient method of injecting dependencies into the class that you are going to test. The injection happens by setter/constructor or as a last resort by reflection.

In the above example as the Service class has the UserRepository dependency and there is a @Mock defined:

@Mock
private UserRepository userRepository;

Mockito will inject that into the Service.

@Spy is like @Mock, except that it allows you to selectively mock only certain bahvior and by default the real implementation is invoked.

In my case i used it to mock the findUserbyUsername method of the Service as what is going inside is not important in our two tests:

when(service.findUserbyUsername(defaultName)).thenReturn(userStub);
Maciej Kowalski
  • 25,605
  • 12
  • 54
  • 63
  • So I should use this kind of test (using mockito I mean) for all of my Service/Controller methods? So I don't actually use DB just mocks of repositories? – doublemc Feb 09 '17 at 23:21
  • Yes. If you actually want to include database or any other external dependencies of a class your testing, then you would be doing Integration Tests. This is very important also, but first you need to have a good unit test coverage first, then you move level up and start writing higher level integration tests. – Maciej Kowalski Feb 10 '17 at 05:55
  • And could you explain why do you use @ Spy and @ InjectMocks annotations? I found some info about them but it's still very unclear. – doublemc Feb 10 '17 at 10:21
  • Also added edit to my post, I had to change the verify - is that ok? – doublemc Feb 10 '17 at 10:40
  • I have added the Spy and IncjectMocks info.. Can you add the UserRepository.delete(User) code? with imports please – Maciej Kowalski Feb 10 '17 at 10:46
  • Added them in EDIT2. Also I have changed your tests a bit, becasue my findUserByUsername is private so I used findLoggedInUser - provided code for both in edit as well. Could you check if my slightly changed tests are ok? – doublemc Feb 10 '17 at 10:51
  • I meant the source code of delete method from the UserRepository, not Service – Maciej Kowalski Feb 10 '17 at 10:55
  • Added my userRepository. Also I changed my edits: I am now getting error when doing tests - I changed your code a bit because I found that findUserByUsername method is useless becasue I have very similiar in my UserRepository so changed my code a bit. – doublemc Feb 10 '17 at 11:05
  • Ok try this: verify(userRepository, times(0)).delete(any(User.class)); – Maciej Kowalski Feb 10 '17 at 11:18
  • Unfortunately, still getting an error. Posted Error in EDIT 5. – doublemc Feb 10 '17 at 12:24
  • In your shouldReturnTrue_whenUserDeleted method you should have verify(userRepository, times(1)).delete(any(User.class); It should be '1' instead of '0' – Maciej Kowalski Feb 10 '17 at 12:30
  • I created three more tests for two methods and I'm not sure if they make any sense. They pass but I don't know if I'm testing the right thing and using verify on right methods. Could you check them for me please? Added them to EDIT 6. – doublemc Feb 10 '17 at 23:44
  • Especially I don't know what method should I verify in shouldReturnFalse_whenUserNotFoundByUsername because I'm invoking userRepository.findByUsername in my when statement in both tests so is there even a point to verify number of invocations? It's all so confusing to be honest, it just shows that my code sucks becasue it's so had to test, right? – doublemc Feb 10 '17 at 23:57