Argument captors in JUnit - Build an Automation Script
import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock UserRepository userRepository; @InjectMocks UserService userService; @Test void testCreateUser_callsRepositoryWithCorrectUser() { // Arrange User expectedUser = new User("Alice", "alice@example.com"); // Act userService.createUser("Alice", "alice@example.com"); // Assert ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class); verify(userRepository, times(1)).save(userCaptor.capture()); User capturedUser = userCaptor.getValue(); assertEquals(expectedUser.getName(), capturedUser.getName()); assertEquals(expectedUser.getEmail(), capturedUser.getEmail()); } } // Supporting classes class User { private String name; private String email; public User(String name, String email) { this.name = name; this.email = email; } public String getName() { return name; } public String getEmail() { return email; } } interface UserRepository { void save(User user); } class UserService { private final UserRepository userRepository; public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public void createUser(String name, String email) { User user = new User(name, email); userRepository.save(user); } }
This test uses Mockito with JUnit 5 to verify that the UserService calls the save method of UserRepository with the correct User object.
We use @ExtendWith(MockitoExtension.class) to enable Mockito annotations.
The @Mock annotation creates a mock UserRepository. The @InjectMocks annotation creates a UserService instance and injects the mock repository.
In the test method, we call createUser on the service. Then we use ArgumentCaptor to capture the User object passed to save.
We verify that save was called exactly once. Then we assert that the captured User has the expected name and email.
This approach helps us check the exact argument passed to a mocked method, which is useful when the argument is created inside the method under test.
Now add data-driven testing with 3 different user inputs to verify the repository is called correctly each time
Practice
ArgumentCaptor in JUnit tests with Mockito?Solution
Step 1: Understand ArgumentCaptor role
An ArgumentCaptor is used to catch the actual arguments passed to a mock's method during test execution.Step 2: Differentiate from other Mockito features
Unlike mocks or verify count, ArgumentCaptor focuses on capturing and inspecting method arguments.Final Answer:
To capture and inspect the arguments passed to a mocked method -> Option AQuick Check:
ArgumentCaptor captures arguments [OK]
- Confusing ArgumentCaptor with mock creation
- Thinking it counts method calls
- Assuming it stubs return values
ArgumentCaptor for a String argument in JUnit with Mockito?Solution
Step 1: Recall ArgumentCaptor creation syntax
The correct way is to use the static methodforClass()with the class type.Step 2: Check each option for syntax correctness
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); usesArgumentCaptor.forClass(String.class), which is correct. Options B and C incorrectly use constructors, which are not public. ArgumentCaptor<String> captor = ArgumentCaptor.capture(String.class); uses a non-existent methodcapture()for creation.Final Answer:
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class); -> Option AQuick Check:
Use forClass() to create captor [OK]
- Trying to instantiate ArgumentCaptor with new
- Using non-existent capture() method
- Omitting the class type in forClass()
List<String> list = mock(List.class);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
list.add("hello");
verify(list).add(captor.capture());
String captured = captor.getValue();Solution
Step 1: Understand the mock and method call
The mock list hasadd("hello")called, so "hello" is passed as argument.Step 2: Verify and capture argument
Theverify(list).add(captor.capture())captures the argument passed toadd(), which is "hello".Final Answer:
"hello" -> Option BQuick Check:
Captured argument matches method call input [OK]
- Assuming captor captures method name
- Expecting null if not stubbed
- Thinking it throws exception without stubbing
ArgumentCaptor<Integer> captor = ArgumentCaptor.forClass(Integer.class); verify(mockedList).add(captor.capture()); mockedList.add(5); Integer value = captor.getValue();
Solution
Step 1: Analyze order of method call and verify
Theverify()call must come after the actual method call to check interactions.Step 2: Identify the mistake
Here,verify()is called beforemockedList.add(5), so no interaction is recorded yet.Final Answer:
Theverifycall is before the actual method call -> Option DQuick Check:
Verify after method call [OK]
- Calling verify before method invocation
- Assuming captor resets automatically
- Declaring captor after verify
Solution
Step 1: Understand multiple call verification with ArgumentCaptor
A single ArgumentCaptor can capture all arguments from multiple calls usinggetAllValues().Step 2: Evaluate options for capturing multiple arguments
Use a single ArgumentCaptor and callgetAllValues()after verifying two calls correctly uses one captor and retrieves all captured arguments. Create two separate ArgumentCaptors and verify each call separately is unnecessary and verbose. Use ArgumentCaptor only once; it cannot capture multiple calls is false; captor supports multiple captures. Capture arguments by stubbing the method instead of verify is incorrect because stubbing does not capture arguments.Final Answer:
Use a single ArgumentCaptor and callgetAllValues()after verifying two calls -> Option CQuick Check:
Single captor + getAllValues() captures multiple calls [OK]
- Creating multiple captors unnecessarily
- Thinking captor captures only one call
- Trying to capture arguments via stubbing
Ready to prove it?
Pass the test and this pattern moves unknown
