Mockito spy()

Introduction

The spy() method in Mockito is used to create spy objects. A spy is a partial mock, which means that it can call real methods while still allowing certain methods to be stubbed. This is particularly useful when you want to test real method behavior while controlling the behavior of specific methods. This tutorial will demonstrate how to use the spy() method in Mockito to create and configure spy objects.

Maven Dependencies

To use Mockito with JUnit 5, add the following dependencies to your pom.xml file:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>4.8.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>4.8.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.9.2</version>
    <scope>test</scope>
</dependency>

Example Scenario

We will create a BankAccount class with methods for depositing and withdrawing money. Our goal is to test the BankAccount methods using the spy() method in Mockito to create a spy object that calls real methods while allowing specific methods to be stubbed.

BankAccount Class

First, create the BankAccount class.

public class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
        }
    }
}

JUnit 5 Test Class with Mockito

Create a test class for BankAccount using JUnit 5 and Mockito.

import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MockitoExtension.class)
public class BankAccountTest {

    @Spy
    private BankAccount bankAccountSpy = new BankAccount(100.0);

    @Test
    public void testDeposit() {
        // Given
        // No need to stub the deposit method, we want to use the real method

        // When
        bankAccountSpy.deposit(50.0);

        // Then
        assertEquals(150.0, bankAccountSpy.getBalance());
    }

    @Test
    public void testWithdraw() {
        // Given
        doNothing().when(bankAccountSpy).withdraw(50.0);

        // When
        bankAccountSpy.withdraw(50.0);

        // Then
        assertEquals(100.0, bankAccountSpy.getBalance());
    }

    @Test
    public void testWithdrawRealMethod() {
        // Given
        // No stubbing for withdraw, so the real method will be called

        // When
        bankAccountSpy.withdraw(50.0);

        // Then
        assertEquals(50.0, bankAccountSpy.getBalance());
    }
}

Explanation

  1. Creating Spies with spy():

    • The @Spy annotation creates a spy instance of the BankAccount class with an initial balance of 100.0. Alternatively, you can use BankAccount bankAccountSpy = spy(new BankAccount(100.0)); to create a spy instance.
    • A spy calls real methods by default, but you can still stub specific methods to control their behavior.
  2. Testing Real Method Behavior:

    • The testDeposit() method tests the real deposit method of the BankAccount class. Since no stubbing is needed, the real method is called and its behavior is verified.
  3. Stubbing Specific Methods:

    • The testWithdraw() method uses doNothing().when(bankAccountSpy).withdraw(50.0); to stub the withdraw method. This controls the behavior of the withdraw method, making it do nothing instead of calling the real method.
  4. Verifying Real Method Calls:

    • The testWithdrawRealMethod() method tests the real withdraw method without any stubbing. The real method is called and its behavior is verified.

Additional Scenarios

Scenario: Combining Real and Stubbed Methods

In this scenario, we will demonstrate how to combine real and stubbed methods in a single test.

@Test
public void testDepositAndWithdraw() {
    // Given
    doNothing().when(bankAccountSpy).withdraw(50.0);

    // When
    bankAccountSpy.deposit(50.0);
    bankAccountSpy.withdraw(50.0);

    // Then
    assertEquals(150.0, bankAccountSpy.getBalance()); // Real deposit method
    // Balance remains 150.0 because withdraw was stubbed
}

Explanation

  1. Stubbing a Specific Method:

    • doNothing().when(bankAccountSpy).withdraw(50.0); stubs the withdraw method to do nothing when called with 50.0.
  2. Calling Real Methods:

    • The deposit method is called without stubbing, so the real method is executed, and its behavior is verified.
  3. Combining Real and Stubbed Methods:

    • Both real and stubbed methods are combined in a single test, demonstrating the flexibility of using spies.

Conclusion

The spy() method in Mockito allows you to create partial mocks, enabling you to call real methods while still being able to stub specific methods. By using spy(), you can test the behavior of your code more comprehensively, combining real and controlled method behavior. This step-by-step guide demonstrated how to effectively use the spy() method in your unit tests, covering different scenarios to ensure comprehensive testing of the BankAccount class.

Related Mockito Methods

Mockito mock()
Mockito spy()
Mockito when()
Mockito thenThrow()
Mockito verify()
Mockito times()
Mockito never()
Mockito any()
Mockito eq()
Mockito inOrder()
Mockito doReturn()
Mockito doThrow()
Mockito doAnswer()
Mockito timeout()
Mockito ArgumentMatchers

Comments