Question

How to Mock Void Methods with Mockito in Java

javaunit-testingmockingmockitovoid

Question

How can I mock methods that have a void return type using Mockito?

I implemented something like an observer pattern, but I am not sure how to test it correctly with Mockito.

I looked for examples online, but I could not find one that clearly matched my case.

My code is roughly like this:

public class World {

    List<Listener> listeners;

    void addListener(Listener item) {
        listeners.add(item);
    }

    void doAction(Action goal, Object obj) {
        setState("i received");
        goal.doAction(obj);
        setState("i finished");
    }

    private String state;
    // getter and setter for state
}

public class WorldTest implements Listener {

    @Test
    public void word() {
        World w = mock(World.class);
        w.addListener(this);
        // ...
    }
}

interface Listener {
    void doAction();
}

The expected behavior is not happening when World is mocked.

I want to observe the system state changes and make assertions based on them. How should I test or mock this properly with Mockito?

Short Answer

By the end of this page, you will understand how Mockito handles void methods, when to use doNothing(), doThrow(), and doAnswer(), and why fully mocking the class under test often prevents real behavior from running. You will also learn how to test state changes and interactions correctly in an observer-style design.

Concept

Mockito can mock void methods, but they work a little differently from methods that return a value.

For a normal method, you often write:

when(service.getName()).thenReturn("Alice");

That style does not work for void methods, because there is no return value to configure.

Instead, Mockito provides the do...when(...) syntax:

doNothing().when(mock).someVoidMethod();
doThrow(new RuntimeException()).when(mock).someVoidMethod();
doAnswer(invocation -> {
    // custom behavior
    return null;
}).when(mock).someVoidMethod();

Why this matters

In real code, void methods are common for:

  • notifications
  • logging
  • sending events
  • mutating object state
  • updating other objects

Testing them usually means one of these:

  • verifying that the method was called
  • simulating side effects
  • checking how other code reacts when the method throws an exception

The key issue in your example

The main problem is not just mocking a void method. It is that you are mocking World itself:

World w = mock(World.class);

When you fully mock World, its real methods do not run unless you explicitly tell Mockito to call them. That means:

  • addListener() does not really add anything
  • doAction() does not really update state
  • setState("i received") and setState("i finished") do not happen

So if you want to test the behavior of World, you usually should not mock World. Instead:

  • create a real World object
  • mock or stub its collaborators, such as Action or Listener
  • assert the resulting state or verify interactions

That is a core testing principle:

  • mock dependencies
  • do not mock the class you want to test

Mental Model

Think of a mock as a cardboard stand-in for a real object.

It looks like the object from the outside, but it does not do any real work unless you tell it exactly what to do.

So if you create:

World w = mock(World.class);

that mock is like an empty actor reading no lines. Calling:

w.doAction(...);

does not execute your real doAction() logic.

A better mental model is:

  • Real object under test = the machine you want to inspect
  • Mocks = fake buttons, sensors, or connected devices around it

If you want to see whether the machine changes state correctly, use the real machine. If you want to control or observe the things connected to it, mock those.

Take Quiz

Syntax and Examples

With Mockito, void methods are configured using do...when(...).

Core syntax

doNothing().when(mock).voidMethod();
doThrow(new IllegalStateException()).when(mock).voidMethod();
doAnswer(invocation -> {
    // custom side effect
    return null;
}).when(mock).voidMethod();

Example 1: Verify that a void method was called

import static org.mockito.Mockito.*;

Listener listener = mock(Listener.class);

listener.doAction();

verify(listener).doAction();

This is the most common case. You do not always need to stub a void method at all. Often, you simply call the code and verify the interaction.

Example 2: Make a void method throw an exception

import static org.mockito.Mockito.*;

Listener listener = mock(Listener.class);
doThrow(new RuntimeException("failed")).when(listener).doAction();

listener.doAction(); // throws RuntimeException

This is useful for testing error handling.

Example 3: Custom behavior with doAnswer()

import static org.mockito.Mockito.*;
import java.util.concurrent.atomic.AtomicBoolean;

Listener listener = mock(Listener.class);
AtomicBoolean called = new AtomicBoolean(false);

doAnswer(invocation -> {
    called.set(true);
    return null;
}).when(listener).doAction();

listener.doAction();

System.out.println(called.get()); // true

Example 4: Testing a real class with mocked dependency

A better design for your scenario looks like this:

public interface Action {
    void doAction(Object obj);
}

public class World {
    private String state;

    public void doAction(Action goal, Object obj) {
        setState("i received");
        goal.doAction(obj);
        setState("i finished");
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}

Test:

import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;

public class WorldTest {

    @Test
    public void shouldUpdateStateBeforeAndAfterAction() {
        World world = new World();
        Action action = mock(Action.class);

        doNothing().when(action).doAction("data");

        world.doAction(action, "data");

        verify(action).doAction("data");
        assertEquals("i finished", world.getState());
    }
}

Here:

  • World is real
  • Action is mocked
  • the state change can be observed
  • the void dependency call can be verified

Step by Step Execution

Consider this code:

public interface Action {
    void doAction(Object obj);
}

public class World {
    private String state;

    public void doAction(Action goal, Object obj) {
        setState("i received");
        goal.doAction(obj);
        setState("i finished");
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}

And this test:

World world = new World();
Action action = mock(Action.class);

world.doAction(action, "file.txt");

What happens step by step

  1. world is a real object.
  2. action is a mock.
  3. world.doAction(action, "file.txt") is called.
  4. Inside World.doAction(...), state becomes "i received".
  5. Then goal.doAction(obj) runs.
  6. Since action is a Mockito mock, the call is recorded.
  7. By default, a mocked void method does nothing unless stubbed otherwise.
  8. Execution continues.
  9. state becomes "i finished".
  10. After the method finishes:
    • you can verify that action.doAction("file.txt") was called
    • you can assert that world.getState() is "i finished"

Trace example with assertion

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;

World world = new World();
Action action = mock(Action.class);

world.doAction(action, "file.txt");

verify(action).doAction("file.txt");
assertEquals("i finished", world.getState());

If you mock World instead

World world = mock(World.class);
Action action = mock(Action.class);

world.doAction(action, "file.txt");

Now the real doAction() body does not run, so:

  • state is never updated
  • action.doAction("file.txt") is never called by real logic

That is why the expected behavior seems missing.

Real World Use Cases

void method mocking appears in many practical situations.

Event and observer systems

A listener often reacts through a void method:

listener.onMessage(message);

Tests usually verify that the listener was notified.

Logging and auditing

Services may call:

auditService.record(userAction);

The method returns nothing, but tests may verify it was called with the right data.

Email or notification sending

emailSender.sendWelcomeEmail(user);

You usually do not want to send a real email in a test, so you mock the sender and verify the call.

External side effects

Examples include:

  • writing files
  • publishing events
  • sending metrics
  • updating caches

These methods often return void, so mocks prevent real side effects while still letting tests check behavior.

Exception-path testing

A void dependency may fail:

doThrow(new RuntimeException("SMTP down")).when(emailSender).sendWelcomeEmail(user);

This helps test retry logic, fallback behavior, or error reporting.

Take Quiz

Real Codebase Usage

In real projects, developers rarely mock everything. They usually test one unit of behavior at a time.

Common pattern: real class, mocked dependencies

OrderService service = new OrderService(paymentGateway, emailSender);

In a test:

  • OrderService is real
  • paymentGateway may be mocked
  • emailSender may be mocked

This lets the service logic run while dependencies stay controlled.

Verifying void interactions

Common in service tests:

verify(emailSender).sendWelcomeEmail(user);
verify(auditService).record("USER_CREATED");

Using doThrow() for error handling

Teams often test failure branches like this:

doThrow(new IOException()).when(fileWriter).write(data);

Then they check whether the application:

  • logs the error
  • retries
  • returns a safe response
  • throws a domain-specific exception

Guard clauses and validation

A method may stop before a void dependency is called:

if (user == null) {
    return;
}
emailSender.sendWelcomeEmail(user);

Test:

verify(emailSender, never()).sendWelcomeEmail(any());

Capturing arguments

Sometimes you care about what was passed into a void method:

ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(logger).info(captor.capture());

This is common for events, logging, and notifications.

Partial mocking with spies

If needed, developers sometimes use a spy:

World world = spy(new World());

A spy runs real code by default, but selected methods can be stubbed. This is useful when you want most real behavior but need to intercept one part. However, spies should be used carefully because they can make tests harder to understand.

Common Mistakes

1. Mocking the class you want to test

Broken approach:

World world = mock(World.class);
world.doAction(action, "data");

Why it fails:

  • the real doAction() method does not run
  • state is not updated

Better:

World world = new World();
Action action = mock(Action.class);

2. Using when(...).thenReturn(...) on a void method

Broken code:

when(listener.doAction()).thenReturn(...);

Why it fails:

  • void methods do not return a value

Correct approach:

doNothing().when(listener).doAction();

or simply verify the call without stubbing.

3. Expecting a mock to keep real state

Broken assumption:

World world = mock(World.class);
world.setState("hello");
assertEquals("hello", world.getState());

Why it fails:

Mocks do not behave like normal objects unless configured.

4. Forgetting to initialize collections

In your original class:

List<Listener> listeners;

If you call listeners.add(item) before initialization, you get a NullPointerException.

Fix:

private List<Listener> listeners = new ArrayList<>();

5. Testing state and interaction in the wrong place

If your goal is to check World state changes, test the real World.

If your goal is to check whether a listener or action was called, mock that collaborator.

6. Using a spy when a plain real object is enough

A spy is sometimes useful, but beginners often use it when a normal object would be simpler. Prefer the simplest test setup first.

Comparisons

ApproachWhat it doesGood forCommon risk
mock(Class)Creates a fake object with no real behavior by defaultIsolating dependenciesReal logic does not run
spy(new Class())Wraps a real object and calls real methods by defaultPartial mockingCan make tests complex
Real objectExecutes real code normallyTesting state changes and business logicHarder if dependencies are external
Mockito syntaxUsed forExample
when(...).thenReturn(...)Non-void methodswhen(repo.find()).thenReturn(user)
doNothing().when(...)Void methodsdoNothing().when(sender).send()
doThrow().when(...)Void methods that should faildoThrow(ex).when(sender).send()
doAnswer().when(...)Void methods with custom side effectsdoAnswer(...).when(listener).doAction()

verify vs stubbing for void methods

TechniquePurpose
verify(mock).method()Check that a void method was called
doNothing().when(mock).method()Explicitly say it should do nothing
doThrow(...).when(mock).method()Simulate failure
doAnswer(...).when(mock).method()Simulate custom behavior or capture effects

In many tests, verify(...) is enough and explicit doNothing() is not necessary.

Take Quiz

Cheat Sheet

Quick rules

  • Use do...when(...) for void methods
  • Use when(...).thenReturn(...) for non-void methods
  • Do not mock the class you want to test unless you have a specific reason
  • Mock collaborators, not the main behavior under test

Void method syntax

doNothing().when(mock).voidMethod();
doThrow(new RuntimeException()).when(mock).voidMethod();
doAnswer(invocation -> {
    return null;
}).when(mock).voidMethod();

Verify calls

verify(mock).voidMethod();
verify(mock, never()).voidMethod();
verify(mock, times(2)).voidMethod();

Best practice for your scenario

World world = new World();
Action action = mock(Action.class);

world.doAction(action, "data");

verify(action).doAction("data");
assertEquals("i finished", world.getState());

Common edge cases

  • Mocked objects do not keep real state
  • Real methods do not run on a plain mock
  • void methods cannot use thenReturn(...)
  • Collections must still be initialized in real objects

Useful extras

ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
verify(action).doAction(captor.capture());
doThrow(new IllegalStateException("boom")).when(action).doAction(any());

FAQ

How do you mock a void method in Mockito?

Use doNothing(), doThrow(), or doAnswer() with when(...), for example:

doThrow(new RuntimeException()).when(service).run();

Can I use when(...).thenReturn(...) with a void method?

No. That syntax only works for methods that return a value.

Why is my mocked object not changing state?

Because a Mockito mock does not run real code by default. If you want real state changes, use a real object or a spy.

Should I mock the class I am testing?

Usually no. Create a real instance of the class under test and mock its dependencies.

Do I need doNothing() for every void method?

No. Mockito does nothing by default for mocked void methods. Often you can skip stubbing and just use verify(...).

How do I test that a void method was called with the right argument?

Use verify(...) or ArgumentCaptor.

When should I use doAnswer()?

Use it when you want a mocked void method to perform custom logic, such as toggling a flag, capturing state, or simulating a side effect.

What is the difference between a mock and a spy?

A mock has no real behavior unless stubbed. A spy wraps a real object and calls real methods by default.

Take Quiz

Mini Project

Description

Build a small Java test scenario where a World object updates its internal state before and after calling a dependency with a void method. This demonstrates the correct testing strategy: keep the main object real, mock the collaborator, and verify both state changes and interactions.

Goal

Create a test that confirms a void dependency is called and that the main object's state changes as expected.

Requirements

  1. Create a World class with a state field and a doAction method.
  2. Create an Action interface with a void doAction(Object obj) method.
  3. Write a JUnit test that uses a real World and a mocked Action.
  4. Verify that the mocked Action is called with the expected argument.
  5. Assert that the final state value is correct after execution.
Take Quiz

Keep learning

Related questions

View more