Mockito Spy Explained: A Comprehensive Guide

In the world of testing, mock objects are used to simulate the behavior of real objects in the application. Mockito is a popular framework for creating mock objects in Java. A Spy in Mockito is a type of mock object that allows you to create a partial mock of an object by spying on an existing object. This means that you can use the Spy to monitor and verify interactions between the object and other objects in the system.

What is a Spy in Mockito?

A Spy in Mockito is a type of mock object that wraps an existing object. It allows you to call real methods on the object and still be able to verify that the method calls were made. A Spy can be useful when you want to test a method that calls other methods on the same object, but you don’t want to mock all the methods.

When you create a Spy, Mockito creates a new instance of the class and wraps the existing object. The Spy has the same behavior as the real object, but you can use Mockito’s verification methods to check which methods were called.

How to create a Spy in Mockito?

To create a Spy in Mockito, you can use the spy() method. Here’s an example of how to create a Spy for a UsersServiceImpl class:

UsersServiceImpl userService = new UsersServiceImpl();
UsersServiceImpl spyUserService = spy(userService);

In this example, we create a new instance of the UsersServiceImpl class and then create a Spy using the spy() method. The spyUserService object now wraps the userService object, and we can use it to call the real methods on userService and still be able to verify that the method calls were made.

Now to Use Spy Object?

So how to use Spy object with the UsersServiceImpl class and how to verify that a method was called?

Lets assume that we have the following UsersServiceImpl class for reference:

public class UsersServiceImpl implements UsersService {
    
    @Override
    public boolean createUser(User user) {
        // implementation
        return true;
    }
    
    @Override
    public boolean updateUser(User user) {
        // implementation
        return true;
    }
}

To use a Spy object for the UsersServiceImpl class, you can create a new instance of the class and then create a Spy using the spy() method:

UsersServiceImpl userService = new UsersServiceImpl();
UsersServiceImpl spyUserService = spy(userService);

Once you have the Spy object, you can call the real methods of the UsersServiceImpl class, and Mockito will record the method calls. For example, to call the createUser() method, you can do the following:

User user = new User("John", "Doe");
spyUserService.createUser(user);

This will call the real createUser() method of the UsersServiceImpl class and create a new user.

Verify Method Call

To verify that the createUser() method was called, you can use the verify() method. Here’s an example:

User user = new User("John", "Doe");
spyUserService.createUser(user);
verify(spyUserService).createUser(user);

In this example, we create a new user using the Spy object, and then we use the verify() method to check if the createUser() method was called with the same user object. If the method was called, the test will pass; otherwise, it will fail.

You can use the same approach to spy on the updateUser() method as well:

User user = new User("John", "Doe");
spyUserService.updateUser(user);
verify(spyUserService).updateUser(user);

This will call the real updateUser() method of the UsersServiceImpl class and update the user. The verify() method will then check if the method was called with the same user object.

Spy vs Mock in Mockito

A Mock in Mockito is a complete simulation of an object. When you create a Mock, all the methods of the object are replaced with stubs that you define. This means that when you call a method on the Mock, it will always return the value that you specified.

A Spy, on the other hand, is a partial mock of an object. When you create a Spy, you can call real methods on the object, and the Spy will behave like the real object. This can be useful when you want to test a method that calls other methods on the same object, but you don’t want to mock all the methods.

Spy vs Stub in Mockito

A Stub in Mockito is a type of mock object that returns a fixed value when a method is called. A Stub is useful when you want to test a method that depends on the result of another method, but you don’t want to test the other method itself.

A Spy, on the other hand, is a partial mock of an object that allows you to call real methods on the object. This can be useful when you want to test a method that calls other methods on the same object, but you don’t want to mock all the methods.

Can you use a Spy to verify method calls on a real object?

Yes, you can use a Spy to verify method calls on a real object. When you create a Spy, Mockito records all method calls on the real object. You can then use Mockito’s verification methods, such as verify(), to verify that the method calls occurred.

Can you use a Spy to modify the behaviour of a real object?

Yes, you can use a Spy to modify the behavior of a real object. When you create a Spy, you can use Mockito’s when() method to stub specific methods of the real object and provide custom behavior for them.

Suppose you want to modify the behavior of the createUser() method to always return false, regardless of the user object that is passed in. You can create a Spy object for the UsersServiceImpl class and then use Mockito’s when() method to stub the createUser() method and provide the custom behavior:

UserRepository userRepository = mock(UserRepository.class);
UsersServiceImpl userService = new UsersServiceImpl(userRepository);
UsersServiceImpl spyUserService = spy(userService);

// Stub the createUser() method to always return false
when(spyUserService.createUser(any(User.class))).thenReturn(false);

// Call the createUser() method with a user object
User user = new User("John", "Doe", "[email protected]");
boolean isUserCreated = spyUserService.createUser(user);

// Verify that the createUser() method was called with the same user object
verify(spyUserService).createUser(user);

// Assert that the createUser() method returned false
assertFalse(isUserCreated);

In this example, we create a Spy object for the UsersServiceImpl class and stub the createUser() method to always return false. We then call the createUser() method with a user object and verify that the method was called with the same user object. Finally, we assert that the method returned false, as expected.

Note that in this example, we use the any() method to match any user object that is passed in to the createUser() method. You can also use specific user objects to match the method call if needed.

How do you reset a Spy in Mockito?

You can reset a Spy in Mockito using the reset() method. This method clears all recorded interactions and stubbing for the Spy, allowing you to start fresh. However, it’s generally not recommended to use reset() in your tests, as it can make your tests more complex and harder to maintain.

Here’s an example of how to reset a Spy object for the UsersServiceImpl class:

UserRepository userRepository = mock(UserRepository.class);
UsersServiceImpl userService = new UsersServiceImpl(userRepository);
UsersServiceImpl spyUserService = spy(userService);

// Stub the createUser() method to always return false
when(spyUserService.createUser(any(User.class))).thenReturn(false);

// Call the createUser() method with a user object
User user = new User("John", "Doe", "[email protected]");
boolean isUserCreated = spyUserService.createUser(user);

// Verify that the createUser() method was called with the same user object
verify(spyUserService).createUser(user);

// Reset the spy object
Mockito.reset(spyUserService);

// Stub the createUser() method to always return true
when(spyUserService.createUser(any(User.class))).thenReturn(true);

// Call the createUser() method with a different user object
User user2 = new User("Jane", "Doe", "[email protected]");
boolean isUserCreated2 = spyUserService.createUser(user2);

// Verify that the createUser() method was called with the second user object
verify(spyUserService).createUser(user2);

// Assert that the createUser() method returned true for the second user object
assertTrue(isUserCreated2);

In this example, we create a Spy object for the UsersServiceImpl class and stub the createUser() method to always return false. We then call the createUser() method with a user object and verify that the method was called with the same user object. We then reset the Spy object using the Mockito.reset() method and stub the createUser() method to always return true. Finally, we call the createUser() method with a different user object and verify that the method was called with the second user object and returned true.

Note that when you reset a Spy object using Mockito.reset(), all stubbed methods are reset to their default behavior, and all verified invocations are forgotten. So, be careful when resetting Spy objects, and make sure to use it only when needed.

Video lessons

If you like video tutorials, then have a look at the following video course: “Testing Java with JUnit and Mockito“. This video course is for absolute beginners and you do not need to have any prior knowledge of testing Java applications to enrol.

Conclusion

In summary, a Spy in Mockito is a type of mock object that allows you to create a partial mock of an object by spying on an existing object. You can use the Spy to monitor and verify interactions between the object and other objects in the system. When you create a Spy, Mockito creates a new instance of the class and wraps the existing object. You can then call real methods on the object and still be able to verify that the method calls were made. A Spy can be useful when you want to test a method that calls other methods on the same object, but you don’t want to mock all the methods.

Take your testing skills to new heights with our extensive collection of Mockito tutorials on the Testing Java Code page. From mocking to stubbing, these resources will guide you through the process of utilizing Mockito effectively, resulting in more efficient and reliable testing outcomes.