When we write unit tests that use mock objects, we follow these steps:

  1. Configure the behavior of our mock objects.
  2. Invoke the tested method.
  3. Verify that the correct methods of our mock objects were invoked.

The description of the third step is actually a bit misleading, because often we end up verifying that the correct methods were invoked AND that the other methods of our mock objects were not invoked.

And everyone knows that if we want to write bug free software, we have to verify both of these things or bad things happen.

Right?

Let’s Verify Everything

Let’s start by taking a look at the implementation of a service method that is used to add new user accounts to the database.

The requirements of this service method are:

  • If the email address of the registered user account is not unique, our service method must throw an exception.
  • If the registered user account has a unique email address, our service method must add a new user account to the database.
  • If the registered user account has a unique email address and it is created by using normal sign in, our service method must encode the user’s password before it is saved to the database.
  • If the registered user account has a unique email address and it is created by using social sign in, our service method must save the used social sign in provider.
  • A user account that was created by using social sign in must not have a password.
  • Our service method must return the information of the created user account.

If you want learn how you can specify the requirements of a service method, you should read the following blog posts:

This service method is implemented by following these steps:

  1. The service method checks that the email address given by user is not found from the database. It does this by invoking the findByEmail() method of the UserRepository interface.
  2. If the User object is found, the service method method throws a DuplicateEmailException.
  3. It creates a new User object. If the registration is made by using a normal sign in (the signInProvider property of the RegistrationForm class is not set), the service method encodes the password provided by user, and sets the encoded password to the created User object.
  4. The service methods saves the information of the created User object to the database and returns the saved User object.

The source code of the RepositoryUserService class looks as follows:

 1import org.springframework.beans.factory.annotation.Autowired;
 2import org.springframework.security.crypto.password.PasswordEncoder;
 3import org.springframework.stereotype.Service;
 4import org.springframework.transaction.annotation.Transactional;
 5
 6@Service
 7public class RepositoryUserService implements UserService {
 8
 9    private PasswordEncoder passwordEncoder;
10
11    private UserRepository repository;
12
13    @Autowired
14    public RepositoryUserService(PasswordEncoder passwordEncoder, UserRepository repository) {
15        this.passwordEncoder = passwordEncoder;
16        this.repository = repository;
17    }
18
19    @Transactional
20    @Override
21    public User registerNewUserAccount(RegistrationForm userAccountData) throws DuplicateEmailException {
22        if (emailExist(userAccountData.getEmail())) {
23            throw new DuplicateEmailException("The email address: " + userAccountData.getEmail() + " is already in use.");
24        }
25
26        String encodedPassword = encodePassword(userAccountData);
27
28	    User registered = User.getBuilder()
29				.email(userAccountData.getEmail())
30				.firstName(userAccountData.getFirstName())
31				.lastName(userAccountData.getLastName())
32				.password(encodedPassword)
33				.signInProvider(userAccountData.getSignInProvider())
34				.build();
35
36        return repository.save(registered);
37    }
38
39    private boolean emailExist(String email) {
40        User user = repository.findByEmail(email);
41
42        if (user != null) {
43            return true;
44        }
45
46        return false;
47    }
48
49    private String encodePassword(RegistrationForm dto) {
50        String encodedPassword = null;
51
52        if (dto.isNormalRegistration()) {
53            encodedPassword = passwordEncoder.encode(dto.getPassword());
54        }
55
56        return encodedPassword;
57    }
58}

If we want write unit tests which ensure that our service method is working correctly when the user is registering a new user account by using social sign in AND we want to verify every interaction between our service method and our mock objects, we have to write eight unit tests for it.

We have to ensure that:

  • The service methods checks that email address is unique when a duplicate email address is given.
  • The DuplicateEmailException is thrown when a duplicate email address is given.
  • The service method doesn’t save a new account to the database when a duplicate email address is given.
  • Our service method doesn’t encode the user’s password if a duplicate email address is given.
  • Our service method checks that the email address is unique when a unique email address is given.
  • When a unique email address is given, our service method creates a new User object that contains the correct information and saves the information of the created User object to the database.
  • When a unique email address is given, our service method returns the information of the created user account.
  • When a unique email address is given and a social sign in is used, our service method must not set the password of the created user account (or encode it).

The source code of our test class looks as follows:

  1import net.petrikainulainen.spring.social.signinmvc.user.dto.RegistrationForm;
  2import net.petrikainulainen.spring.social.signinmvc.user.dto.RegistrationFormBuilder;
  3import net.petrikainulainen.spring.social.signinmvc.user.model.SocialMediaService;
  4import net.petrikainulainen.spring.social.signinmvc.user.model.User;
  5import net.petrikainulainen.spring.social.signinmvc.user.repository.UserRepository;
  6import org.junit.Before;
  7import org.junit.Test;
  8import org.junit.runner.RunWith;
  9import org.mockito.ArgumentCaptor;
 10import org.mockito.Mock;
 11import org.mockito.invocation.InvocationOnMock;
 12import org.mockito.runners.MockitoJUnitRunner;
 13import org.mockito.stubbing.Answer;
 14import org.springframework.security.crypto.password.PasswordEncoder;
 15
 16import static com.googlecode.catchexception.CatchException.catchException;
 17import static com.googlecode.catchexception.CatchException.caughtException;
 18import static net.petrikainulainen.spring.social.signinmvc.user.model.UserAssert.assertThatUser;
 19import static org.assertj.core.api.Assertions.assertThat;
 20import static org.mockito.Matchers.isA;
 21import static org.mockito.Mockito.never;
 22import static org.mockito.Mockito.times;
 23import static org.mockito.Mockito.verify;
 24import static org.mockito.Mockito.verifyZeroInteractions;
 25import static org.mockito.Mockito.when;
 26
 27@RunWith(MockitoJUnitRunner.class)
 28public class RepositoryUserServiceTest {
 29
 30    private static final String REGISTRATION_EMAIL_ADDRESS = "john.smith@gmail.com";
 31    private static final String REGISTRATION_FIRST_NAME = "John";
 32    private static final String REGISTRATION_LAST_NAME = "Smith";
 33    private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;
 34
 35    private RepositoryUserService registrationService;
 36
 37    @Mock
 38    private PasswordEncoder passwordEncoder;
 39
 40    @Mock
 41    private UserRepository repository;
 42
 43    @Before
 44    public void setUp() {
 45        registrationService = new RepositoryUserService(passwordEncoder, repository);
 46    }
 47
 48    @Test
 49    public void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldCheckThatEmailIsUnique() throws DuplicateEmailException {
 50        RegistrationForm registration = new RegistrationFormBuilder()
 51                .email(REGISTRATION_EMAIL_ADDRESS)
 52                .firstName(REGISTRATION_FIRST_NAME)
 53                .lastName(REGISTRATION_LAST_NAME)
 54                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
 55                .build();
 56
 57        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(new User());
 58
 59        catchException(registrationService).registerNewUserAccount(registration);
 60
 61        verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);
 62    }
 63
 64    @Test
 65    public void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldThrowException() throws DuplicateEmailException {
 66        RegistrationForm registration = new RegistrationFormBuilder()
 67                .email(REGISTRATION_EMAIL_ADDRESS)
 68                .firstName(REGISTRATION_FIRST_NAME)
 69                .lastName(REGISTRATION_LAST_NAME)
 70                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
 71                .build();
 72
 73        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(new User());
 74
 75        catchException(registrationService).registerNewUserAccount(registration);
 76
 77        assertThat(caughtException()).isExactlyInstanceOf(DuplicateEmailException.class);
 78    }
 79
 80    @Test
 81    public void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount() throws DuplicateEmailException {
 82        RegistrationForm registration = new RegistrationFormBuilder()
 83                .email(REGISTRATION_EMAIL_ADDRESS)
 84                .firstName(REGISTRATION_FIRST_NAME)
 85                .lastName(REGISTRATION_LAST_NAME)
 86                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
 87                .build();
 88
 89        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(new User());
 90
 91        catchException(registrationService).registerNewUserAccount(registration);
 92
 93        verify(repository, never()).save(isA(User.class));
 94    }
 95
 96    @Test
 97    public void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotCreateEncodedPasswordForUser() throws DuplicateEmailException {
 98        RegistrationForm registration = new RegistrationFormBuilder()
 99                .email(REGISTRATION_EMAIL_ADDRESS)
100                .firstName(REGISTRATION_FIRST_NAME)
101                .lastName(REGISTRATION_LAST_NAME)
102                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
103                .build();
104
105        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(new User());
106
107        catchException(registrationService).registerNewUserAccount(registration);
108
109        verifyZeroInteractions(passwordEncoder);
110    }
111
112    @Test
113    public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCheckThatEmailIsUnique() throws DuplicateEmailException {
114        RegistrationForm registration = new RegistrationFormBuilder()
115                .email(REGISTRATION_EMAIL_ADDRESS)
116                .firstName(REGISTRATION_FIRST_NAME)
117                .lastName(REGISTRATION_LAST_NAME)
118                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
119                .build();
120
121        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
122
123        registrationService.registerNewUserAccount(registration);
124
125        verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);
126    }
127
128    @Test
129    public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldSaveNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {
130        RegistrationForm registration = new RegistrationFormBuilder()
131                .email(REGISTRATION_EMAIL_ADDRESS)
132                .firstName(REGISTRATION_FIRST_NAME)
133                .lastName(REGISTRATION_LAST_NAME)
134                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
135                .build();
136
137        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
138
139        registrationService.registerNewUserAccount(registration);
140
141        ArgumentCaptor<User> userAccountArgument = ArgumentCaptor.forClass(User.class);
142        verify(repository, times(1)).save(userAccountArgument.capture());
143
144        User createdUserAccount = userAccountArgument.getValue();
145
146        assertThatUser(createdUserAccount)
147                .hasEmail(REGISTRATION_EMAIL_ADDRESS)
148                .hasFirstName(REGISTRATION_FIRST_NAME)
149                .hasLastName(REGISTRATION_LAST_NAME)
150                .isRegisteredUser()
151                .isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);
152    }
153
154    @Test
155    public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldReturnCreatedUserAccount() throws DuplicateEmailException {
156        RegistrationForm registration = new RegistrationFormBuilder()
157                .email(REGISTRATION_EMAIL_ADDRESS)
158                .firstName(REGISTRATION_FIRST_NAME)
159                .lastName(REGISTRATION_LAST_NAME)
160                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
161                .build();
162
163        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
164
165        when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {
166            @Override
167            public User answer(InvocationOnMock invocation) throws Throwable {
168                Object[] arguments = invocation.getArguments();
169                return (User) arguments[0];
170            }
171        });
172
173        User createdUserAccount = registrationService.registerNewUserAccount(registration);
174
175        assertThatUser(createdUserAccount)
176                .hasEmail(REGISTRATION_EMAIL_ADDRESS)
177                .hasFirstName(REGISTRATION_FIRST_NAME)
178                .hasLastName(REGISTRATION_LAST_NAME)
179                .isRegisteredUser()
180                .isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);
181    }
182
183    @Test
184    public void registerNewUserAccount_SocialSignInAnUniqueEmail_ShouldNotCreateEncodedPasswordForUser() throws DuplicateEmailException {
185        RegistrationForm registration = new RegistrationFormBuilder()
186                .email(REGISTRATION_EMAIL_ADDRESS)
187                .firstName(REGISTRATION_FIRST_NAME)
188                .lastName(REGISTRATION_LAST_NAME)
189                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
190                .build();
191
192        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
193
194        registrationService.registerNewUserAccount(registration);
195
196        verifyZeroInteractions(passwordEncoder);
197    }
198}
These unit tests are written by following the instructions given in the previous parts of this tutorial.

That class has a lot of unit tests. Are we sure that every one of them is really necessary?

Or Maybe Not

The obvious problem is that we wrote two unit tests which both verify that our service method checks that the email address given by the user is unique. We could fix this by combining these tests into a single unit test. After all, one test should convince us that our service method verifies that the email address given by user is unique before it creates a new user account.

However, if we do this, we won’t find an answer to a much more interesting question. This question is:

Should we really verify every interaction between the tested code and our mock objects?

A few months I ago I ran into an article titled: Why Most Unit Testing is Waste by James Coplien. This article makes several good points but one of them was suits very well in this situation. James Coplien argued that we should ask one question about every test in our test suite:

If this test fails, what business requirement is compromised?

He also explains why this is such an important question:

Most of the time, the answer is, “I don’t know.” If you don’t know the value of the test, then the test theoretically could have zero business value. The test does have a cost: maintenance, computing time, administration, and so forth. That means the test could have net negative value. That is the fourth category of tests to remove.

Let’s find out what happens when we evaluate our unit tests by using this question.

Popping Up the Question

When the ask the question: “If this test fails, what business requirement is compromised?” about every unit test of our test class, we get the following answers:

  • The service method checks that email address is unique when a duplicate email address is given.
    • User must have a unique email address.
  • The DuplicateEmailException is thrown when a duplicate email address is given.
    • User must have a unique email address.
  • The service method doesn’t save a new account to the database when a duplicate email address is given.
    • User must have a unique email address.
  • Our service method doesn’t encode the user’s password if a duplicate email address is given.
  • Our service method checks that the email address is unique when a unique email address is given.
    • User must have a unique email address.
  • When a unique email address is given, our service method creates a new User object that contains the correct information and saves the information of the created User object to the used database.
    • If the registered user account has unique email address, it must be saved to the database.
    • If the registered user account is created by using social sign in, our service method must save the used social sign in provider.
  • When a unique email address is given, our service method returns the information of the created user account.
    • Our service method must return the information of the created user account.
  • When a unique email address is given and a social sign in is used, our service method must not set the password of the created user account (or encode it).
    • User account that is created by using social sign in has no password.

At first it looks like our test class has only one unit test that has no business value (or which might have a negative net value). This unit test ensures that there are no interactions between our code and the PasswordEncoder mock when a user tries to create a new user account by using a duplicate email address.

It is clear that we must delete this unit test, but this is not the only unit test that must be deleted.

The Rabbit Hole Is Deeper than Expected

Earlier we noticed that our test class contains two unit tests that both verify that the findByEmail() method of the UserRepository interface is called. When we take a closer look at the implementation of the tested service method, we notice that:

  • Our service method throws a DuplicateEmailException when the findByEmail() method of the UserRepository interface returns a User object.
  • Our service method creates a new user account when the findByEmail() method of the UserRepository interface returns null.

The relevant part of the tested service method looks as follows:

 1public User registerNewUserAccount(RegistrationForm userAccountData) throws DuplicateEmailException {
 2	if (emailExist(userAccountData.getEmail())) {
 3		//If the PersonRepository returns a Person object, an exception is thrown.
 4		throw new DuplicateEmailException("The email address: " + userAccountData.getEmail() + " is already in use.");
 5	}
 6
 7	//If the PersonRepository returns null, the execution of this method continues.
 8}
 9
10private boolean emailExist(String email) {
11	User user = repository.findByEmail(email);
12
13	if (user != null) {
14		return true;
15	}
16
17	return false;
18}

I argue that we should remove both of these unit tests because of two reasons:

  • As long as we have configured the PersonRepository mock correctly, we know that its findByEmail() method was called by using the correct method parameter. Although we can link these test cases to a business requirement (user’s email address must be unique), we don’t need them to verify that this business requirement isn’t compromised.
  • These unit tests don’t document the API of our service method. They document its implementation. Tests like this are harmful because they litter our test suite with irrelevant tests and they make refactoring harder.

If we don’t configure our mock objects, they return “nice” values. The Mockito FAQ states that:

In order to be transparent and unobtrusive all Mockito mocks by default return ’nice’ values. For example: zeros, falseys, empty collections or nulls. Refer to javadocs about stubbing to see exactly what values are returned by default.

This why we should always configure the relevant mock objects! If we don’t do so, our tests might be useless.

Let’s move on and clean up this mess.

Cleaning Up the Mess

After we have removed these unit tests from our test class, its source code looks as follows:

  1import org.junit.Before;
  2import org.junit.Test;
  3import org.junit.runner.RunWith;
  4import org.mockito.ArgumentCaptor;
  5import org.mockito.Mock;
  6import org.mockito.invocation.InvocationOnMock;
  7import org.mockito.runners.MockitoJUnitRunner;
  8import org.mockito.stubbing.Answer;
  9import org.springframework.security.crypto.password.PasswordEncoder;
 10
 11import static com.googlecode.catchexception.CatchException.catchException;
 12import static com.googlecode.catchexception.CatchException.caughtException;
 13import static org.assertj.core.api.Assertions.assertThat;
 14import static org.mockito.Matchers.isA;
 15import static org.mockito.Mockito.never;
 16import static org.mockito.Mockito.times;
 17import static org.mockito.Mockito.verify;
 18import static org.mockito.Mockito.verifyZeroInteractions;
 19import static org.mockito.Mockito.when;
 20
 21@RunWith(MockitoJUnitRunner.class)
 22public class RepositoryUserServiceTest {
 23
 24    private static final String REGISTRATION_EMAIL_ADDRESS = "john.smith@gmail.com";
 25    private static final String REGISTRATION_FIRST_NAME = "John";
 26    private static final String REGISTRATION_LAST_NAME = "Smith";
 27    private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;
 28
 29    private RepositoryUserService registrationService;
 30
 31    @Mock
 32    private PasswordEncoder passwordEncoder;
 33
 34    @Mock
 35    private UserRepository repository;
 36
 37    @Before
 38    public void setUp() {
 39        registrationService = new RepositoryUserService(passwordEncoder, repository);
 40    }
 41
 42    @Test
 43    public void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldThrowException() throws DuplicateEmailException {
 44        RegistrationForm registration = new RegistrationFormBuilder()
 45                .email(REGISTRATION_EMAIL_ADDRESS)
 46                .firstName(REGISTRATION_FIRST_NAME)
 47                .lastName(REGISTRATION_LAST_NAME)
 48                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
 49                .build();
 50
 51        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(new User());
 52
 53        catchException(registrationService).registerNewUserAccount(registration);
 54
 55        assertThat(caughtException()).isExactlyInstanceOf(DuplicateEmailException.class);
 56    }
 57
 58    @Test
 59    public void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount() throws DuplicateEmailException {
 60        RegistrationForm registration = new RegistrationFormBuilder()
 61                .email(REGISTRATION_EMAIL_ADDRESS)
 62                .firstName(REGISTRATION_FIRST_NAME)
 63                .lastName(REGISTRATION_LAST_NAME)
 64                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
 65                .build();
 66
 67        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(new User());
 68
 69        catchException(registrationService).registerNewUserAccount(registration);
 70
 71        verify(repository, never()).save(isA(User.class));
 72    }
 73
 74    @Test
 75    public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldSaveNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {
 76        RegistrationForm registration = new RegistrationFormBuilder()
 77                .email(REGISTRATION_EMAIL_ADDRESS)
 78                .firstName(REGISTRATION_FIRST_NAME)
 79                .lastName(REGISTRATION_LAST_NAME)
 80                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
 81                .build();
 82
 83        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
 84
 85        registrationService.registerNewUserAccount(registration);
 86
 87        ArgumentCaptor<User> userAccountArgument = ArgumentCaptor.forClass(User.class);
 88        verify(repository, times(1)).save(userAccountArgument.capture());
 89
 90        User createdUserAccount = userAccountArgument.getValue();
 91
 92        assertThatUser(createdUserAccount)
 93                .hasEmail(REGISTRATION_EMAIL_ADDRESS)
 94                .hasFirstName(REGISTRATION_FIRST_NAME)
 95                .hasLastName(REGISTRATION_LAST_NAME)
 96                .isRegisteredUser()
 97                .isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);
 98    }
 99
100    @Test
101    public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldReturnCreatedUserAccount() throws DuplicateEmailException {
102        RegistrationForm registration = new RegistrationFormBuilder()
103                .email(REGISTRATION_EMAIL_ADDRESS)
104                .firstName(REGISTRATION_FIRST_NAME)
105                .lastName(REGISTRATION_LAST_NAME)
106                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
107                .build();
108
109        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
110
111        when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {
112            @Override
113            public User answer(InvocationOnMock invocation) throws Throwable {
114                Object[] arguments = invocation.getArguments();
115                return (User) arguments[0];
116            }
117        });
118
119        User createdUserAccount = registrationService.registerNewUserAccount(registration);
120
121        assertThatUser(createdUserAccount)
122                .hasEmail(REGISTRATION_EMAIL_ADDRESS)
123                .hasFirstName(REGISTRATION_FIRST_NAME)
124                .hasLastName(REGISTRATION_LAST_NAME)
125                .isRegisteredUser()
126                .isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);
127    }
128
129    @Test
130    public void registerNewUserAccount_SocialSignInAnUniqueEmail_ShouldNotCreateEncodedPasswordForUser() throws DuplicateEmailException {
131        RegistrationForm registration = new RegistrationFormBuilder()
132                .email(REGISTRATION_EMAIL_ADDRESS)
133                .firstName(REGISTRATION_FIRST_NAME)
134                .lastName(REGISTRATION_LAST_NAME)
135                .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
136                .build();
137
138        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
139
140        registrationService.registerNewUserAccount(registration);
141
142        verifyZeroInteractions(passwordEncoder);
143    }
144}

We removed three unit tests from our test class, and as a result, we can enjoy the following benefits:

  • Our test class has less unit unit tests. This might seem like a strange benefit because often we are advised to write as many unit tests as possible. However, if we think about this, having less unit tests makes sense because we have less tests to maintain. This and the fact that each unit tests only one thing makes our code easier to maintain and refactor.
  • We have improved the quality of our documentation. The removed unit tests didn’t document the public API of the tested service method. They documented its implementation. Because these tests were removed, it is easier to figure out the requirements of the tested service method.

Summary

This blog post has taught us three things:

  • If we cannot identify the business requirement that is compromised if a unit test fails, we shouldn’t write that test.
  • We should not write unit tests that don’t document the public API of the tested method because these tests make our code (and tests) harder to maintain and refactor.
  • If we find existing unit tests that break these two rules, we should delete them.

We have achieved a lot during this tutorial. Do you think that it is possible to make these unit tests even better?