3

I'm trying to understand how to use Mockito in a Spring project, but I'm a bit stuck with the following:

I'm about to test a service with a real (in-memory) repository. I'm mocking every other object that's being used by that service. If I understand correctly, annotating an object with @Mock will be mocked, @Spy will use a real object, and with @InjectMocks will kind of "autowire" these annotated objects. However, I think I misunderstood @Spy because the following code is not working (seems like a mock object is inserted, neither a DB interaction, neither a NullPointerException comes up):

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

    @Spy
    @Autowired
    private UserRepository userRepository;

    @Mock
    private MessageService messageService;

    @InjectMocks
    private UserService userService = new UserServiceImpl();

    @Test
    public void testUserRegistration() {
        userService.registerUser("[email protected]");

        Assert.assertEquals("There is one user in the repository after the first registration.", 1, userRepository.count());
    }
}

If I remove the @Spy annotation and insert the repository in the service with reflection, it works:

ReflectionTestUtils.setField(userService, "userRepository", userRepository);

This seems inconvenient, and I'm pretty sure that @Spy is meant to do this. Can anyone point out what I'm missing/misunderstanding?

Edit: one more thing - with the @Spy annotation I'm not getting a NullPointerException on save, but it seems like a mock object is inserted instead of the real repository.

Edit 2: I think this is the answer for my confusion:

SpringBoot Junit bean autowire

6
  • Why do you have Autowired and Spy? Could you expand on "not working"? Commented Jul 26, 2018 at 7:12
  • Pretty sure you should be using @Mock above your repository Commented Jul 26, 2018 at 7:13
  • 1
    If at all practical, it's better to use constructor injection for precisely this reason--you don't need Spring for this test and can simply create and pass a mock to new UserServiceImpl(mockUserRepository). Commented Jul 26, 2018 at 7:14
  • @chrylis this would mean that I have to modify all the services, because field injection is used everywhere, and I'd like to avoid that. Commented Jul 26, 2018 at 7:20
  • @ThomasWithers I'm trying to use an in-memory db for testing, Mock wouldn't do what I want, would it? Commented Jul 26, 2018 at 7:21

1 Answer 1

2

The problem is that you probably @Autowired some services/repositories in UserService like these:

public class UserServiceImpl() {

  @Autowired
  private UserRepository userRepository;
  @Autowired
  private MessageService messageService;

  // ... all the stuff
}

Mockito tries to inject mocks through constructor or setters which are not available. Try to redesign your class UserServiceImpl, so it has constructor like:

public class UserServiceImpl() {

  private final UserRepository userRepository;
  private final MessageService messageService;

  @Autowired
  public UserServiceImpl(UserRepository userRepository, MessageService messageService) {
    this.userRepository = userRepository;
    this.messageService = messageService;
  }

Then you can instantiate the service in @Before with the constructor using those mocks.

Sign up to request clarification or add additional context in comments.

5 Comments

So the @Spy and @Autowired annotations on the repository in the test are correct by the way, my only problem is the field autowiring?
If you setup in-memory database in test context in the configuration, then I don't see a reason to use @Spy on the repository, since it is already a mock of real database. And yes I think that the only problem with that is field autowiring instead of constructor/setter. Also I'll highly recommend to use constructor injections.
Also, now I see that you are missing MockitoAnnotations.initMocks(this) in @Before
I actually missed the existence of @MockBean annotation and basically I wanted to do it manually by combining some. :)
Also if I try to exclude the Spring parts from the test as much as I can and use MockitoJUnitRunner instead of SpringRunner I won't need to watch out for these if I'm correct.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.