自定义答案与回调
使用 `Answer` 接口为模拟方法调用实现自定义逻辑,以模拟复杂行为。
自定义答案与回调 是 CoddyKit 上的免费 Testing Mastery: JUnit, Mockito & Integration Tests 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Testing Mastery: JUnit, Mockito & Integration Tests 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Testing Mastery: JUnit, Mockito & Integration Tests 课程共包含 4 节课。
本课时的部分内容尚未翻译,以英文显示。
Beyond Simple Stubbing
Sometimes, simply returning a fixed value with when().thenReturn() isn't enough for your mock objects.
- What if a mock method needs to modify an argument passed to it?
- What if it needs to execute a callback function?
- What if its return value depends on multiple inputs in a complex, dynamic way?
These advanced scenarios call for custom logic within your mocks.
Introducing Mockito's Answer
Mockito provides the org.mockito.stubbing.Answer interface. This powerful tool lets you define custom behavior for a mocked method call.
- Think of it as writing a small piece of code that runs whenever the mocked method is invoked.
- The
Answerinterface has a single method:Object answer(Invocation invocation) throws Throwable;.
It gives you fine-grained control over mock responses.
Anatomy of answer()
The answer() method is where you implement your custom logic. It receives an InvocationOnMock object (often just referred to as invocation).
- The
InvocationOnMockobject provides crucial details about the method call:- Which method was called?
- What arguments were passed?
- Which mock object was involved?
You can use these details to decide what to return, throw an exception, or perform side effects.
Using doAnswer for Flexibility
While you can use when().thenAnswer(), Mockito's doAnswer() method is often more flexible and widely used, especially for void methods or when combining with other stubbings.
doAnswer()allows you to specify behavior for a method call before definingwhen().- It's particularly useful when you need to:
- Capture arguments.
- Invoke callbacks.
- Change the state of objects passed as arguments.
Custom Return Logic
Let's see how to use doAnswer to return a value that depends on the input argument. Here, our mock will double the input number.
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
// A simple interface to mock
interface Calculator {
int calculate(int input);
}
public class Main {
public static void main(String[] args) {
// Create a mock object for our Calculator interface
Calculator mockCalculator = Mockito.mock(Calculator.class);
// Define custom answer to double the input argument
Mockito.doAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
// Get the first argument (index 0) as an Integer
Integer arg = invocation.getArgument(0);
return arg * 2; // Return double the argument
}
}).when(mockCalculator).calculate(Mockito.anyInt()); // Apply to any int input
// Test the custom behavior of the mock
System.out.println("Result for 5: " + mockCalculator.calculate(5));
System.out.println("Result for 10: " + mockCalculator.calculate(10));
}
}Simulating Callbacks
A very common and powerful use case for doAnswer is simulating asynchronous operations or invoking callbacks.
- Imagine a service that takes a
Callbackinterface (e.g., aConsumer) as an argument. - You can use
doAnswerto manually invoke theonSuccessoronFailuremethod of that callback.
This allows you to test how your application code reacts to different callback outcomes without actual asynchronous execution.
Mocking a Callback
Here, we simulate a DataService calling a Consumer callback. doAnswer lets us trigger the callback with a specific value when fetchData is called.
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.function.Consumer;
// An interface representing a service that fetches data asynchronously
interface DataService {
void fetchData(String query, Consumer<String> callback);
}
public class Main {
public static void main(String[] args) {
DataService mockService = Mockito.mock(DataService.class);
// Configure the mock to simulate fetching data and calling the callback
Mockito.doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
// Get the arguments passed to fetchData
String query = invocation.getArgument(0); // The first argument is the query
Consumer<String> callback = invocation.getArgument(1); // The second is the callback
// Simulate a successful data fetch by invoking the callback
callback.accept("Data for '" + query + "' fetched successfully!");
return null; // Void methods in doAnswer return null
}
}).when(mockService).fetchData(Mockito.anyString(), Mockito.any(Consumer.class));
// Our application code would typically use this service
System.out.println("Application requesting user data...");
mockService.fetchData("user:456", result -> {
System.out.println("Application received data: " + result);
});
System.out.println("Application finished request setup.");
}
}Deeper with Invocation
The InvocationOnMock object (or simply invocation) passed to your answer() method is a powerful context object.
invocation.getArguments(): Returns an array of all arguments passed to the mocked method.invocation.getArgument(index): Returns a specific argument by its index.invocation.getMethod(): Gives you theMethodobject that was called, allowing reflection.invocation.getMock(): Returns the mock object itself.invocation.callRealMethod(): For spies, this allows invoking the actual, real method.
Tips for Answer Use
Custom answers are powerful, but it's important to use them thoughtfully to keep your tests maintainable and clear:
- Keep them simple: If your
answerlogic becomes too complex, it might indicate a design issue in your production code or an overly complicated test setup. - Prefer simpler stubbing: Always choose simpler stubbing methods like
thenReturn(),thenThrow(), orthenCallRealMethod()if they can achieve the desired behavior. - Test the
Answeritself: For very complexAnswerimplementations, consider extracting them into a separate class and writing a unit test for that class.
When to Use Answer
Here are typical situations where the Answer interface (usually via doAnswer()) truly shines:
- Dynamic return values: When the return value depends on input arguments in a non-trivial or calculated way.
- Side effects: Modifying arguments that are passed by reference (e.g., updating an object).
- Callbacks: Triggering success or failure callbacks to simulate asynchronous operations.
- Chaining operations: Simulating a sequence of interactions or state changes within a single mock call.
Custom Mock Behavior
You are testing a UserService that depends on a UserRepository. The UserRepository has a method save(User user) which typically updates the user's ID after saving it to a database.
You want to mock UserRepository to simulate this behavior without hitting a real database. Assume a User class with setId(Long id).
Recap: Dynamic Mocks
You've learned how to bring dynamic and complex behavior to your mocks using Mockito's Answer interface and the doAnswer method.
- The
Answerinterface lets you define custom logic that executes when a mocked method is called. - The
InvocationOnMockobject provides essential context, including method arguments and the mock itself. doAnsweris ideal for dynamic return values, simulating callbacks, or modifying arguments passed by reference.
Remember to use `Answer` judiciously, preferring simpler stubbing when possible, to keep your tests clear and focused.
常见问题解答
「自定义答案与回调」课时是免费的吗?
是的 — 「自定义答案与回调」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Testing Mastery: JUnit, Mockito & Integration Tests 课程的其余内容,请升级到 CoddyKit PRO。 Testing Mastery: JUnit, Mockito & Integration Tests 课程共包含 4 节课。
「自定义答案与回调」这节课中我会学到什么?
使用 `Answer` 接口为模拟方法调用实现自定义逻辑,以模拟复杂行为。 你通过在浏览器中直接运行的动手代码来练习 Testing Mastery: JUnit, Mockito & Integration Tests,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Testing Mastery: JUnit, Mockito & Integration Tests 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Testing Mastery: JUnit, Mockito & Integration Tests 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。
「自定义答案与回调」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Testing Mastery: JUnit, Mockito & Integration Tests 课中编写并运行代码吗?
能。每节 Testing Mastery: JUnit, Mockito & Integration Tests 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。