1. Overview
1.概述
When mocking a method in Java, it can be useful to receive different responses based on the parameters passed in. In this article, we’ll look at different ways to achieve that goal depending on the complexity of our requirements.
当在 Java 中模拟一个方法时,根据传入的参数接收不同的响应可能非常有用。在本文中,我们将根据需求的复杂程度,探讨实现这一目标的不同方法。
2. Setup
2.设置
First, let’s create an example service which we want to mock:
首先,让我们创建一个要模拟的示例服务:
class ExampleService {
int getValue(int arg){
return 1;
}
}
We’ve got a very simple service with a single method. The method has a single int as an argument and returns an int. Note that the argument and the return value have no relation, so by default, it’ll always return 1.
我们有一个非常简单的服务,只有一个方法。该方法的参数是一个 int,返回值是一个 int。请注意,参数和返回值没有任何关系,因此默认情况下,它将始终返回 1。
3. Limitations of Consecutive Stubbing
3.连续插杆的局限性
Let’s look at consecutive stubbing and what we can and can’t do with it. We can use consecutive stubbing to get different arguments from our mock in order regardless of the input we provide. This obviously lacks control over matching particular inputs to desired outputs but is useful in many cases. To do this we pass the method we want to stub to when(). We then chain a call to thenReturn() providing the responses in the order we want them:
我们可以使用连续存根从我们的 mock 中依次获取不同的参数,而不管我们提供的输入是什么。这显然无法控制特定输入与所需输出的匹配,但在许多情况下非常有用。为此,我们将想要存根的方法传递给when()。然后,我们通过链式调用 thenReturn() 按我们希望的顺序提供响应:
@Test
void givenAMethod_whenUsingConsecutiveStubbing_thenExpectResultsInOrder(){
when(exampleService.getValue(anyInt())).thenReturn(9, 18, 27);
assertEquals(9, exampleService.getValue(1));
assertEquals(18, exampleService.getValue(1));
assertEquals(27, exampleService.getValue(1));
assertEquals(27, exampleService.getValue(1));
}
We can see from the assertions that despite always passing in 1 as the parameter, we received back the expected values in order. Once all the values have been returned, all future calls will return the final value, as seen in the fourth call in our test.
从断言中我们可以看到,尽管始终将 1 作为参数传递,我们还是依次返回了预期值。一旦返回了所有值,今后的所有调用都将返回最终值,正如我们测试中的第四次调用所示。
4. Stubbing Calls for Different Parameters
4.不同参数的存根调用
We can extend our use of when() and thenReturn() to return different values for different parameters:
我们可以扩展 when() 和 thenReturn() 的使用范围,为不同的参数返回不同的值:
@Test
void givenAMethod_whenStubbingForMultipleArguments_thenExpectDifferentResults() {
when(exampleService.getValue(10)).thenReturn(100);
when(exampleService.getValue(20)).thenReturn(200);
when(exampleService.getValue(30)).thenReturn(300);
assertEquals(100, exampleService.getValue(10));
assertEquals(200, exampleService.getValue(20));
assertEquals(300, exampleService.getValue(30));
}
The argument for when() is the method we want to stub, along with the value we want to specify a response for. By chaining the call to when() with thenReturn(), we’ve instructed the mock to return the requested value when the correct argument is received. We’re free to apply as many of these as we want to our mock to handle a range of inputs. We’ll receive the requested return value every time the expected input value is provided.
when() 的参数是我们要存根的方法,以及我们要指定响应的值。通过将对 when() 的调用与 thenReturn() 链接, 我们指示 mock 在收到正确参数时返回所请求的值。我们可以自由地在我们的 mock 中应用任意多个这些函数,以处理一系列输入。每次提供预期输入值时,我们都将收到请求的返回值。
5. Using thenAnswer()
5.使用 thenAnswer()
A more complex option, offering maximum control, is to use thenAnswer(). This allows us to take the arguments, perform any computation on them we want, and then return a value that’ll be outputted when interacting with the mock:
提供最大控制的更复杂选项是使用 thenAnswer() 。这允许我们接收参数,对参数执行我们想要的任何计算,然后返回一个值,该值将在与 mock 交互时输出:
@Test
void givenAMethod_whenUsingThenAnswer_thenExpectDifferentResults() {
when(exampleService.getValue(anyInt())).thenAnswer(invocation -> {
int argument = (int) invocation.getArguments()[0];
int result;
switch (argument) {
case 25:
result = 125;
break;
case 50:
result = 150;
break;
case 75:
result = 175;
break;
default:
result = 0;
}
return result;
});
assertEquals(125, exampleService.getValue(25));
assertEquals(150, exampleService.getValue(50));
assertEquals(175, exampleService.getValue(75));
}
Above, we’ve grabbed the arguments using getArguments() on the provided invocation object. We’ve assumed a single int argument here, but we could’ve catered for several different types. We also could’ve checked that there was at least one argument and the cast to an int was successful. To demonstrate the capabilities, we’ve used a switch statement to return different values based on the input. At the bottom, we can see from the assertions that our mocked service returns the results of the switch statement.
上面,我们使用 getArguments() 在提供的调用对象上获取了参数。我们假定这里只有一个 int 参数,但我们也可以考虑多种不同类型的参数。我们还可以检查是否至少有一个参数,以及是否成功将其转换为 int 参数。为了演示这些功能,我们使用了 switch 语句来根据输入返回不同的值。在底部,我们可以从断言中看到,我们的模拟服务返回了 switch 语句的结果。
This option allows us to handle an unlimited amount of inputs with a single call to when(). The sacrifice is the readability and maintainability of the tests.
通过该选项,我们只需调用一次 when() 即可处理无限量的输入。这样做的代价是测试的可读性和可维护性。
6. Conclusion
6.结论
In this tutorial, we’ve seen three ways of configuring a mocked method to return different values. We looked at consecutive stubbing and saw that it’s useful for returning known values in order for any input but is very limited beyond that. Using when() combined with thenReturn() for each potential input offers a simple solution with improved control. Alternatively, we can use thenAnswer() for maximum control over the relationship between the given input and the expected output. All three are useful depending on the requirements under test.
在本教程中,我们看到了配置模拟方法以返回不同值的三种方法。我们查看了连续存根,发现它对于按照任何输入的顺序返回已知值非常有用,但除此之外就非常有限了。使用 when() 结合 thenReturn() 为每个潜在输入提供了一个简单的解决方案,并改进了控制。 另外,我们还可以使用 thenAnswer() 来最大限度地控制给定输入和预期输出之间的关系。根据测试的要求,这三种方法都很有用。
As always, the full code for the examples is available over on GitHub.
与往常一样,这些示例的完整代码可在 GitHub 上获取。