Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unit test a interface implementation with mock in Spring Boot

I'm trying to write a simple unit test for a service in Spring Boot.

The service calls a method on a repository which returns an instance of User. I'm trying to mock the repository, because I want to test only the service.

So, the code for Repository:

public interface UserRepository extends MongoRepository<User, String> {
  User findByEmail(String email);
}

Service interface:

public interface UserService {
  @Async
  CompletableFuture<User> findByEmail(String email) throws InterruptedException;
}

Service implementation:

@Service
public class UserServiceImpl implements UserService {
  private UserRepository userRepository;

  // dependency injection
  // don't need Autowire here
  // https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
  public UserServiceImpl(UserRepository userRepository) {
    this.userRepository = userRepository;
  }

  @Async
  public CompletableFuture<User> findByEmail(String email) throws InterruptedException {
    User user = userRepository.findByEmail(email);
    return CompletableFuture.completedFuture(user);
  }
}

Unit Test:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

  @InjectMocks
  UserService userService;

  @Mock
  UserRepository mockUserRepository;

  @Before
  public void setUp() {
    MockitoAnnotations.initMock(this);
  }

  @Test
  public void mustReturnUser() throws InterruptedException {
    String emailTest = "[email protected]";

    User fakeUser = new User();
    fakeUser.setEmail(emailTest);

    when(mockUserRepository.findByEmail(emailTest)).thenReturn(fakeUser);

    User user = userService.findByEmail(emailTest).join();
    assertThat(user).isEqualTo(fakeUser);

    verify(mockUserRepository).findByEmail(emailTest);
  }
}

When I run this test, I got a MockitoException:

org.mockito.exceptions.base.MockitoException: 
Cannot instantiate @InjectMocks field named 'userService'.
...
Caused by: org.mockito.exceptions.base.MockitoException: the type 'UserService' is an interface.

Instead of using the interface, I tried to use the real implementation; changing the test like this:

@InjectMocks
UserServiceImpl userService;

Now, the test passes with success, but this don't appear be right (at least for me). I like to test the UserService that Spring Boot is using (suppose that in a new version of my system, I implement a new UserServicePostgreSQLImpl - now I'm using MongoDB). (edit: see the bottom edit in the question)

I changed the Unit Test as follows:

@Autowired
@InjectMocks
UserService userService;

but now I got a test failure:

Expected :model.User@383caf89
Actual   :null

For some reason, when I use @Autowired, the UserRepository mock doesn't work. If I change the emailTest to use a real email in my database, the test passes. When I use @Autowired, the test is using the real UserRepository and not a Mock version of UserRepository.

Any help?

Edit: looking at the answers from @msfoster and @dunni, and thinking better, I believe that the correct way is to test every implementation (in my example, use UserServiceImpl userService).

like image 408
Roberto Correia Avatar asked Aug 03 '17 13:08

Roberto Correia


People also ask

How do you mock in spring boot test?

This is easily done by using Spring Boot's @MockBean annotation. The Spring Boot test support will then automatically create a Mockito mock of type SendMoneyUseCase and add it to the application context so that our controller can use it.

How do you unit test a mock?

Advanced: Mocking in Unit TestMocking is used in unit tests to replace the return value of a class method or function. This may seem counterintuitive since unit tests are supposed to test the class method or function, but we are replacing all those processing and setting a predefined output.

Can Mockito mock an interface?

We can use Mockito class mock() method to create a mock object of a given class or interface. This is the simplest way to mock an object.

Can we create mock for interface?

mock() method allows us to create a mock object of a class or an interface. We can then use the mock to stub return values for its methods and verify if they were called.


2 Answers

In order for your UserServiceImpl to be autowired when annotating it with @InjectMocks then it needs to registered as a Spring bean itself. You can do this most simply by annotating your UserServiceImpl class with @Service.

This will ensure it is picked up by the component scan in your Spring boot configuration. (As long as the scan includes the package your service class is in!)

like image 60
Plog Avatar answered Sep 28 '22 00:09

Plog


You are running your tests with SpringRunner but for mocks you don't really need spring context. Try following code

// Using mockito runner
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {

  @Mock
  UserRepository mockUserRepository;

  // Mockito will auto inject mockUserRepository mock to userService via constructor injection
  @InjectMocks
  UserService userService;

  @Test
  public void mustReturnUser() throws InterruptedException {
    String emailTest = "[email protected]";

    User fakeUser = new User();
    fakeUser.setEmail(emailTest);

    when(mockUserRepository.findByEmail(emailTest)).thenReturn(fakeUser);

    User user = userService.findByEmail(emailTest).join();
    assertThat(user).isEqualTo(fakeUser);

    verify(mockUserRepository).findByEmail(emailTest);
  }
}
like image 21
Yogesh Badke Avatar answered Sep 28 '22 00:09

Yogesh Badke