1

Imagine I have the following simple example interface:

public interface UserDB {
    void addUser(User user);
    void updateUser(User user);
    User getUser(String id);
    void deleteUser(String id);
}

I want to write tests with Mockito to test the simple CRUD operations. I want to verify that things like:

  • Update/get/delete work only if the user was added before with 'add'
  • They fail if the user was deleted before with 'delete'.
  • Already created users cannot be created again
  • etc.

My idea is, that I need something like this:

    UserDB udb = mock(UserDB.class);
    when(udb.getUser("1")).thenThrow(new UserNotFoundException());
    when(udb.addUser(new User("1"))).when(udb.getUser("1").thenReturn(new User("1"));

However, things like the last line are not proper Mockito syntax. How can I check verify different results, for different preconditions or different orders of methods called?

durron597
  • 31,968
  • 17
  • 99
  • 158
Kenyakorn Ketsombut
  • 2,072
  • 2
  • 26
  • 43

1 Answers1

1

Doing it this way is a code smell. The fact that you want to write all this code to see if a "user cannot be added twice" is really basically just writing a new class, that has nothing to do with your database rules.

Here is one idea for something you could do instead; structure your validation rules as a Decorator on the database, and then test the decorator itself, with a mock "undecorated" database. For example:

public class ValidatingUserDB implements UserDB {
  private UserDB delegate;

  public ValidatingUserDB(UserDB delegate) {
    this.delegate = delegate;
  }

  public void addUser(User user) {
    User oldUser = delegate.get(user.getId());
    if (oldUser != null) throw new IllegalArgumentException(
             "User " + user.getId() + " already exists!";
    delegate.addUser(user);
  }
}

Then, you would write your tests like this:

@Test(expected=IllegalArgumentException.class)
public void testNoDuplicateUsers() {
  User sampleUser = new User("1");
  UserDB delegate = mock(UserDB.class);
  when(delegate.getUser(any(User.class))).thenReturn(sampleUser);
  UserDB db = new ValidatingUserDB(delegate);
  db.addUser(sampleUser);
}

public void testAddingUser() {
  User sampleUser = new User("1");
  UserDB delegate = mock(UserDB.class);
  UserDB db = new ValidatingUserDB(delegate);
  db.addUser(sampleUser);
  verify(delegate).getUser(sampleUser);
  verify(delegate).addUser(sampleUser);
}

By separating the validation behavior from the CRUD behavior, you enable yourself to write tests in a way that doesn't involve you rewriting all these tests with super complicated answer rules and so forth.

durron597
  • 31,968
  • 17
  • 99
  • 158