1. Introduction
1.介绍
1.1. Overview
1.1.概述
In this post, we’re going to talk about mocking: what it is, why to use it and several exvamples of how to mock the same test case using some of the most used mocking libraries for Java.
在这篇文章中,我们将讨论mocking:它是什么,为什么要使用它,以及如何使用一些最常用的Java mocking库来模拟同一个测试用例的几个例子。
We’ll start with some formal/semi-formal definitions of mocking concepts; then we’ll present the case under test, follow up with examples for each library and end up with some conclusions. The chosen libraries are Mockito, EasyMock, and JMockit.
我们将从一些正式/半正式的嘲弄概念的定义开始;然后我们将介绍被测试的案例,接着是每个库的例子,最后是一些结论。所选择的库是Mockito、EasyMock,以及JMockit。
If you feel that you already know the basics of mocking, maybe you can skip to Point 2 without reading the next three points.
如果你觉得你已经知道了嘲讽的基本原理,也许你可以跳到第2点,而不看后面三点。
1.2. Reasons to Use Mocks
1.2.使用Mock的原因
We’ll start assuming that you already code following some driven development methodology centered on tests (TDD, ATDD or BDD). Or simply that you want to create a test for an existing class that relies on dependencies to achieve its functionality.
我们将开始假设你已经按照一些以测试为中心的驱动开发方法(TDD、ATDD或BDD)进行编码。或者仅仅是你想为一个依靠依赖关系来实现其功能的现有类创建一个测试。
In any case, when unit-testing a class, we want to test only its functionality and not that of its dependencies (either because we trust their implementation or because we’ll test it ourselves).
在任何情况下,当对一个类进行单元测试时,我们只想测试它的功能,而不是其依赖的功能(因为我们相信它们的实现,或者因为我们将自己测试它)。
To achieve this, we need to provide to the object-under-test, a replacement that we can control for that dependency. This way we can force extreme return values, exception throwing or simply reduce time-consuming methods to a fixed return value.
为了实现这一点,我们需要向被测对象提供一个我们可以控制的依赖性的替代物。这样,我们就可以强制执行极端的返回值,抛出异常,或者简单地将耗时的方法减少到一个固定的返回值。
This controlled replacement is the mock, and it will help you to simplify test coding and to reduce test execution time.
这种受控的替换就是mock,它将帮助你简化测试编码,减少测试执行时间。
1.3. Mock Concepts and Definition
1.3.模拟的概念和定义
Let’s see four definitions from an article written by Martin Fowler that sums up the basics everyone should know about mocks:
让我们看看Martin Fowler所写的文章中的四个定义,它总结了每个人都应该知道的关于mocks的基本知识。
- Dummy objects are passed around but never actually used. Usually, they are just used to fill parameter lists.
- Fake objects have working implementations, but usually, take some shortcut which makes them not suitable for production (an in memory database is a good example).
- Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what’s programmed in for the test. Stubs may also record information about calls, such as an email gateway stub that remembers the messages it ‘sent’, or maybe only how many messages it ‘sent’.
- Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
1.4. To Mock or Not to Mock: That Is the Question
1.4.模仿或不模仿 这就是问题所在
Not everything must be mocked. Sometimes it’s better to do an integration test as mocking that method/feature would be just working for little actual benefit. In our test case (that will be shown in the next point) that would be testing the LoginDao.
并不是所有的东西都必须被模拟。有时最好做一个集成测试,因为模拟该方法/功能只是在工作,没有什么实际好处。在我们的测试案例中(将在下一点中展示),这将是测试LoginDao。
The LoginDao would use some third party library for DB access, and mocking it would only consist on assuring that parameters had been prepared for the call, but we still would need to test that the call returns the data we wanted.
LoginDao将使用一些第三方库来访问数据库,嘲弄它将只包括确保参数已准备好用于调用,但我们仍然需要测试该调用是否返回我们想要的数据。
For that reason, it won’t be included in this example (although we could write both the unit test with mock calls for the third party library calls AND an integration test with DBUnit for testing the actual performance of the third party library).
由于这个原因,它不会被包括在这个例子中(尽管我们可以用模拟调用来编写第三方库的单元测试,以及用DBUnit来测试第三方库的实际性能的集成测试)。
2. Test Case
2.测试案例
With everything in the previous section in mind, let’s propose a quite typical test case and how we’ll test it using mocks (when it makes sense to use mocks). This will help us to have a common scenario for later on been able to compare the different mocking libraries.
考虑到上一节的所有内容,让我们提出一个非常典型的测试案例,以及我们将如何使用mock来测试它(当使用mock是有意义的)。这将有助于我们有一个共同的场景,以便以后能够比较不同的嘲讽库。
2.1. Proposed Case
2.1.拟议的案例
The proposed test case will be the login process in an application with a layered architecture.
拟议的测试案例将是一个具有分层结构的应用程序中的登录过程。
The login request will be handled by a controller, that uses a service, which uses a DAO (that looks for user credentials on a DB). We won’t deepen too much into each layer’s implementation and will focus more on the interactions between the components of each layer.
登录请求将由一个控制器处理,该控制器使用一个服务,该服务使用一个DAO(在数据库中寻找用户的证书)。我们不会对每一层的实现进行过多的深入研究,而将更多地关注每一层的组件之间的互动。
This way, we’ll have a LoginController, a LoginService and a LoginDAO. Let’s see a diagram for clarification:
这样,我们将有一个LoginController,一个LoginService和一个LoginDAO。让我们看一张图来澄清一下。
2.2. Implementation
2.2.实施[/span>
We’ll follow now with the implementation used for the test case, so we can understand what’s happening (or what should happen) on the tests.
现在我们将关注用于测试用例的实现,因此我们可以理解在测试中发生了什么(或应该发生什么)。
We’ll start with the model used for all operations, UserForm, that will only hold the user’s name and password (we’re using public access modifiers to simplify) and a getter method for the username field to allow mocking for that property:
我们将从用于所有操作的模型开始,UserForm,它将只保存用户的姓名和密码(我们使用公共访问修饰符来简化),以及username字段的getter方法,以允许对该属性进行嘲弄。
public class UserForm {
public String password;
public String username;
public String getUsername(){
return username;
}
}
Let’s follow with LoginDAO, that will be void of functionality as we only want its methods to be there so we can mock them when needed:
让我们来看看LoginDAO,它的功能将是无效的,因为我们只希望它的方法在那里,以便我们在需要时可以模拟它们。
public class LoginDao {
public int login(UserForm userForm){
return 0;
}
}
LoginDao will be used by LoginService in its login method. LoginService will also have a setCurrentUser method that returns void to test that mocking.
LoginDao将被LoginService在其login方法中使用。LoginService也将有一个setCurrentUser方法,返回void,以测试该嘲弄。
public class LoginService {
private LoginDao loginDao;
private String currentUser;
public boolean login(UserForm userForm) {
assert null != userForm;
int loginResults = loginDao.login(userForm);
switch (loginResults){
case 1:
return true;
default:
return false;
}
}
public void setCurrentUser(String username) {
if(null != username){
this.currentUser = username;
}
}
}
Finally, LoginController will use LoginService for its login method. This will include:
最后,LoginController将使用LoginService来实现其login方法。这将包括。
- a case in which no calls to the mocked service will be done.
- a case in which only one method will be called.
- a case in which all methods will be called.
- a case in which exception throwing will be tested.
public class LoginController {
public LoginService loginService;
public String login(UserForm userForm){
if(null == userForm){
return "ERROR";
}else{
boolean logged;
try {
logged = loginService.login(userForm);
} catch (Exception e) {
return "ERROR";
}
if(logged){
loginService.setCurrentUser(userForm.getUsername());
return "OK";
}else{
return "KO";
}
}
}
}
Now that we’ve seen what is it that we’re trying to test let’s see how we’ll mock it with each library.
现在我们已经看到了我们要测试的是什么,让我们看看我们将如何用每个库来模拟它。
3. Test Setup
3.测试设置
3.1. Mockito
3.1.Mockito
For Mockito we’ll be using version 2.8.9.
对于Mockito,我们将使用2.8.9版本。
The easiest way of creating and using mocks is via the @Mock and @InjectMocks annotations. The first one will create a mock for the class used to define the field and the second one will try to inject said created mocks into the annotated mock.
创建和使用模拟的最简单方法是通过@Mock和@InjectMocks注释。第一个注解将为用于定义字段的类创建一个模拟,第二个注解将尝试将所创建的模拟注入注解的模拟中。
There are more annotations such as @Spy that lets you create a partial mock (a mock that uses the normal implementation in non-mocked methods).
还有更多的注解,比如@Spy,它可以让你创建一个部分模拟(一个在非模拟方法中使用正常实现的模拟)。
That being said, you need to call MockitoAnnotations.initMocks(this) before executing any tests that would use said mocks for all of this “magic” to work. This is usually done in a @Before annotated method. You can also use the MockitoJUnitRunner.
也就是说,你需要在执行任何使用上述模拟的测试之前调用MockitoAnnotations.initMocks(this),以使所有这些 “魔法 “发挥作用。这通常是在@Before注释的方法中完成的。你也可以使用MockitoJUnitRunner。
public class LoginControllerTest {
@Mock
private LoginDao loginDao;
@Spy
@InjectMocks
private LoginService spiedLoginService;
@Mock
private LoginService loginService;
@InjectMocks
private LoginController loginController;
@Before
public void setUp() {
loginController = new LoginController();
MockitoAnnotations.initMocks(this);
}
}
3.2. EasyMock
3.2.EasyMock
For EasyMock, we’ll be using version 3.4 (Javadoc). Note that with EasyMock, for mocks to start “working”, you must call EasyMock.replay(mock) on every test method, or you will receive an exception.
对于 EasyMock,我们将使用3.4版本(Javadoc)。请注意,在EasyMock中,为了使模拟开始 “工作”,你必须在每个测试方法上调用EasyMock.replay(mock),否则你将收到一个异常。
Mocks and tested classes can also be defined via annotations, but in this case, instead of calling a static method for it to work, we’ll be using the EasyMockRunner for the test class.
Mocks和被测类也可以通过注解来定义,但在这种情况下,我们将使用EasyMockRunner来测试类,而不是调用一个静态方法来工作。
Mocks are created with the @Mock annotation and the tested object with the @TestSubject one (which will get its dependencies injected from created mocks). The tested object must be created in-line.
用@Mock注解创建Mock,用@TestSubject注解创建被测对象(它将从创建的Mock中获得它的依赖关系)。被测对象必须是在线创建的。
@RunWith(EasyMockRunner.class)
public class LoginControllerTest {
@Mock
private LoginDao loginDao;
@Mock
private LoginService loginService;
@TestSubject
private LoginController loginController = new LoginController();
}
3.3. JMockit
3.3.JMockit
For JMockit we’ll be using version 1.24 (Javadoc) as version 1.25 hasn’t been released yet (at least while writing this).
对于JMockit,我们将使用1.24版本(Javadoc),因为1.25版本还没有发布(至少在撰写本文时)。
Setup for JMockit is as easy as with Mockito, with the exception that there is no specific annotation for partial mocks (and really no need either) and that you must use JMockit as the test runner.
对JMockit的设置和Mockito一样简单,唯一不同的是没有对部分模拟的特定注解(实际上也没有必要),而且你必须使用JMockit作为测试运行器。
Mocks are defined using the @Injectable annotation (that will create only one mock instance) or with @Mocked annotation (that will create mocks for every instance of the class of the annotated field).
使用@Injectable注解(将只创建一个模拟实例)或使用@Mocked注解(将为被注解字段的每个实例创建模拟)来定义模拟。
The tested instance gets created (and its mocked dependencies injected) using the @Tested annotation.
被测试的实例使用@Tested注解被创建(以及它的模拟依赖被注入)。
@RunWith(JMockit.class)
public class LoginControllerTest {
@Injectable
private LoginDao loginDao;
@Injectable
private LoginService loginService;
@Tested
private LoginController loginController;
}
4. Verifying No Calls to Mock
4.验证没有调用Mock
4.1. Mockito
4.1.Mockito
For verifying that a mock received no calls in Mockito, you have the method verifyNoInteractions() that accepts a mock.
为了验证Mockito中的mock是否没有收到任何调用,你有一个接受mock的方法verifyNoInteractions()。
@Test
public void assertThatNoMethodHasBeenCalled() {
loginController.login(null);
Mockito.verifyNoInteractions(loginService);
}
4.2. EasyMock
4.2.EasyMock
For verifying that a mock received no calls you simply don’t specify behavior, you replay the mock, and lastly, you verify it.
对于验证一个模拟没有收到任何调用,你只需不指定行为,你重放模拟,最后,你验证它。
@Test
public void assertThatNoMethodHasBeenCalled() {
EasyMock.replay(loginService);
loginController.login(null);
EasyMock.verify(loginService);
}
4.3. JMockit
4.3.JMockit
For verifying that a mock received no calls you simply don’t specify expectations for that mock and do a FullVerifications(mock) for said mock.
为了验证一个模拟没有收到任何调用,你只需不指定对该模拟的期望,并对该模拟进行FullVerifications(mock)。
@Test
public void assertThatNoMethodHasBeenCalled() {
loginController.login(null);
new FullVerifications(loginService) {};
}
5. Defining Mocked Method Calls and Verifying Calls to Mocks
5.定义模拟方法的调用和验证对模拟的调用
5.1. Mockito
5.1.Mockito
For mocking method calls, you can use Mockito.when(mock.method(args)).thenReturn(value). Here you can return different values for more than one call just adding them as more parameters: thenReturn(value1, value2, value-n, …).
对于mocking方法调用,你可以使用Mockito.when(mock.method(args)).thenReturn(value)。在这里,你可以为多个调用返回不同的值,只需将它们作为更多的参数。thenReturn(value1, value2, value-n, …)。
Note that you can’t mock void returning methods with this syntax. In said cases, you’ll use a verification of said method (as shown on line 11).
注意,你不能用这种语法来模拟无效返回的方法。在上述情况下,你将使用上述方法的验证(如第11行所示)。
For verifying calls to a mock you can use Mockito.verify(mock).method(args) and you can also verify that no more calls were done to a mock using verifyNoMoreInteractions(mock).
对于验证对模拟的调用,你可以使用Mockito.verify(mock).method(args),你也可以使用verifyNoMoreInteractions(mock)来验证是否对模拟进行了更多调用。
For verifying args, you can pass specific values or use predefined matchers like any(), anyString(), anyInt(). There are a lot more of that kind of matchers and even the possibility to define your matchers which we’ll see in following examples.
对于验证args,你可以传递特定的值或使用预定义的匹配器,如any(), anyString(), anyInt().还有很多这样的匹配器,甚至有可能定义你的匹配器,我们将在下面的例子中看到。
@Test
public void assertTwoMethodsHaveBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
Mockito.when(loginService.login(userForm)).thenReturn(true);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
Mockito.verify(loginService).login(userForm);
Mockito.verify(loginService).setCurrentUser("foo");
}
@Test
public void assertOnlyOneMethodHasBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
Mockito.when(loginService.login(userForm)).thenReturn(false);
String login = loginController.login(userForm);
Assert.assertEquals("KO", login);
Mockito.verify(loginService).login(userForm);
Mockito.verifyNoMoreInteractions(loginService);
}
5.2. EasyMock
5.2.EasyMock
For mocking method calls, you use EasyMock.expect(mock.method(args)).andReturn(value).
对于嘲讽方法调用,你使用EasyMock.expect(mock.method(args)).andReturn(value)。
For verifying calls to a mock, you can use EasyMock.verify(mock), but you must call it always after calling EasyMock.replay(mock).
对于验证对模拟的调用,你可以使用EasyMock.verify(mock),但是你必须在调用EasyMock.replay(mock)之后再调用它。
For verifying args, you can pass specific values, or you have predefined matchers like isA(Class.class), anyString(), anyInt(), and a lot more of that kind of matchers and again the possibility to define your matchers.
对于验证args,你可以传递特定的值,或者你有预定义的匹配器,如isA(Class.class)、anyString()、anyInt(),以及很多这类匹配器,并且可以再次定义你的匹配器。
@Test
public void assertTwoMethodsHaveBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
EasyMock.expect(loginService.login(userForm)).andReturn(true);
loginService.setCurrentUser("foo");
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
EasyMock.verify(loginService);
}
@Test
public void assertOnlyOneMethodHasBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
EasyMock.expect(loginService.login(userForm)).andReturn(false);
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("KO", login);
EasyMock.verify(loginService);
}
5.3. JMockit
5.3.JMockit
With JMockit, you have defined steps for testing: record, replay and verify.
使用JMockit,你已经定义了测试的步骤:记录、重放和验证。
Record is done in a new Expectations(){{}} block (into which you can define actions for several mocks), replay is done simply by invoking a method of the tested class (that should call some mocked object), and verification is done inside a new Verifications(){{}} block (into which you can define verifications for several mocks).
记录是在一个新的Expectations(){{}}块中完成的(在这个块中你可以为几个mock定义动作),重放是通过调用被测类的一个方法(应该调用一些mocked对象)完成的,而验证是在一个新的Verifications(){{}}块中完成的(你可以为几个mock定义核查)。
For mocking method calls, you can use mock.method(args); result = value; inside any Expectations block. Here you can return different values for more than one call just using returns(value1, value2, …, valuen); instead of result = value;.
对于mocking方法调用,你可以在任何Expectations块内使用mock.method(args); result = value;/em>。在这里,你可以为多个调用返回不同的值,只需使用returns(value1, value2, …, valuen);/em>而不是result = value;/em>。
For verifying calls to a mock you can use new Verifications(){{mock.call(value)}} or new Verifications(mock){{}} to verify every expected call previously defined.
对于验证对模拟的调用,你可以使用new Verifications(){{mock.call(value)}}或new Verifications(mock){{}}来验证之前定义的每一个预期调用。
For verifying args, you can pass specific values, or you have predefined values like any, anyString, anyLong, and a lot more of that kind of special values and again the possibility to define your matchers (that must be Hamcrest matchers).
对于验证args,你可以传递特定的值,或者你有预定义的值,如any、anyString、anyLong,以及更多这样的特殊值,并且可以再次定义你的匹配器(那必须是Hamcrest匹配器)。
@Test
public void assertTwoMethodsHaveBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
new Expectations() {{
loginService.login(userForm); result = true;
loginService.setCurrentUser("foo");
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
new FullVerifications(loginService) {};
}
@Test
public void assertOnlyOneMethodHasBeenCalled() {
UserForm userForm = new UserForm();
userForm.username = "foo";
new Expectations() {{
loginService.login(userForm); result = false;
// no expectation for setCurrentUser
}};
String login = loginController.login(userForm);
Assert.assertEquals("KO", login);
new FullVerifications(loginService) {};
}
6. Mocking Exception Throwing
6.嘲笑异常的抛出
6.1. Mockito
6.1.Mockito
Exception throwing can be mocked using .thenThrow(ExceptionClass.class) after a Mockito.when(mock.method(args)).
异常的抛出可以在.thenThrow(ExceptionClass.class)之后使用Mockito.when(mock.method(args))进行模拟。
@Test
public void mockExceptionThrowing() {
UserForm userForm = new UserForm();
Mockito.when(loginService.login(userForm)).thenThrow(IllegalArgumentException.class);
String login = loginController.login(userForm);
Assert.assertEquals("ERROR", login);
Mockito.verify(loginService).login(userForm);
Mockito.verifyNoInteractions(loginService);
}
6.2. EasyMock
6.2.EasyMock
Exception throwing can be mocked using .andThrow(new ExceptionClass()) after an EasyMock.expect(…) call.
在调用EasyMock.expect(…)之后,可以使用.andThrow(new ExceptionClass())来模拟异常的抛出。
@Test
public void mockExceptionThrowing() {
UserForm userForm = new UserForm();
EasyMock.expect(loginService.login(userForm)).andThrow(new IllegalArgumentException());
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("ERROR", login);
EasyMock.verify(loginService);
}
6.3. JMockit
6.3.JMockit
Mocking exception throwing with JMockito is especially easy. Just return an Exception as the result of a mocked method call instead of the “normal” return.
用JMockito来模拟抛出异常是非常容易的。只要返回一个Exception作为模拟方法调用的结果,而不是 “正常 “的返回。
@Test
public void mockExceptionThrowing() {
UserForm userForm = new UserForm();
new Expectations() {{
loginService.login(userForm); result = new IllegalArgumentException();
// no expectation for setCurrentUser
}};
String login = loginController.login(userForm);
Assert.assertEquals("ERROR", login);
new FullVerifications(loginService) {};
}
7. Mocking an Object to Pass Around
7.嘲弄一个物体来传递
7.1. Mockito
7.1.Mockito
You can create a mock also to pass as an argument for a method call. With Mockito, you can do that with a one-liner.
你也可以创建一个模拟,作为一个方法调用的参数传递。有了Mockito,你可以通过一个单行代码来实现。
@Test
public void mockAnObjectToPassAround() {
UserForm userForm = Mockito.when(Mockito.mock(UserForm.class).getUsername())
.thenReturn("foo").getMock();
Mockito.when(loginService.login(userForm)).thenReturn(true);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
Mockito.verify(loginService).login(userForm);
Mockito.verify(loginService).setCurrentUser("foo");
}
7.2. EasyMock
7.2.EasyMock
Mocks can be created in-line with EasyMock.mock(Class.class). Afterward, you can use EasyMock.expect(mock.method()) to prepare it for execution, always remembering to call EasyMock.replay(mock) before using it.
可以用EasyMock.mock(Class.class)在线创建Mock。之后,你可以使用EasyMock.expect(mock.method())来准备执行,在使用之前一定要记得调用EasyMock.replay(mock)。
@Test
public void mockAnObjectToPassAround() {
UserForm userForm = EasyMock.mock(UserForm.class);
EasyMock.expect(userForm.getUsername()).andReturn("foo");
EasyMock.expect(loginService.login(userForm)).andReturn(true);
loginService.setCurrentUser("foo");
EasyMock.replay(userForm);
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
EasyMock.verify(userForm);
EasyMock.verify(loginService);
}
7.3. JMockit
7.3.JMockit
To mock an object for just one method, you can simply pass it mocked as a parameter to the test method. Then you can create expectations as with any other mock.
要为一个方法模拟一个对象,你可以简单地把它作为一个参数传递给测试方法。然后你就可以像其他模拟一样创建期望。
@Test
public void mockAnObjectToPassAround(@Mocked UserForm userForm) {
new Expectations() {{
userForm.getUsername(); result = "foo";
loginService.login(userForm); result = true;
loginService.setCurrentUser("foo");
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
new FullVerifications(loginService) {};
new FullVerifications(userForm) {};
}
8. Custom Argument Matching
8.自定义论据匹配
8.1. Mockito
8.1.Mockito
Sometimes argument matching for mocked calls needs to be a little more complex than just a fixed value or anyString(). For that cases with Mockito has its matcher class that is used with argThat(ArgumentMatcher<>).
有时模拟调用的参数匹配需要比固定值或anyString()更复杂一些。对于这种情况,Mockito有它的匹配器类,可以与argThat(ArgumentMatcher<>)一起使用。
@Test
public void argumentMatching() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// default matcher
Mockito.when(loginService.login(Mockito.any(UserForm.class))).thenReturn(true);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
Mockito.verify(loginService).login(userForm);
// complex matcher
Mockito.verify(loginService).setCurrentUser(ArgumentMatchers.argThat(
new ArgumentMatcher<String>() {
@Override
public boolean matches(String argument) {
return argument.startsWith("foo");
}
}
));
}
8.2. EasyMock
8.2.EasyMock
Custom argument matching is a little bit more complicated with EasyMock as you need to create a static method in which you create the actual matcher and then report it with EasyMock.reportMatcher(IArgumentMatcher).
自定义参数匹配在EasyMock中有点复杂,因为你需要创建一个静态方法,在其中创建实际的匹配器,然后用EasyMock.reportMatcher(IArgumentMatcher)报告它。
Once this method is created, you use it on your mock expectation with a call to the method (like seen in the example in line ).
一旦这个方法被创建,你就可以在你的模拟期望中使用它,并调用这个方法(就像在第1行的例子中看到的那样)。
@Test
public void argumentMatching() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// default matcher
EasyMock.expect(loginService.login(EasyMock.isA(UserForm.class))).andReturn(true);
// complex matcher
loginService.setCurrentUser(specificArgumentMatching("foo"));
EasyMock.replay(loginService);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
EasyMock.verify(loginService);
}
private static String specificArgumentMatching(String expected) {
EasyMock.reportMatcher(new IArgumentMatcher() {
@Override
public boolean matches(Object argument) {
return argument instanceof String
&& ((String) argument).startsWith(expected);
}
@Override
public void appendTo(StringBuffer buffer) {
//NOOP
}
});
return null;
}
8.3. JMockit
8.3.JMockit
Custom argument matching with JMockit is done with the special withArgThat(Matcher) method (that receives Hamcrest‘s Matcher objects).
使用JMockit的自定义参数匹配是通过特殊的withArgThat(Matcher)方法(接收Hamcrest的Matcher对象)完成的。
@Test
public void argumentMatching() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// default matcher
new Expectations() {{
loginService.login((UserForm) any);
result = true;
// complex matcher
loginService.setCurrentUser(withArgThat(new BaseMatcher<String>() {
@Override
public boolean matches(Object item) {
return item instanceof String && ((String) item).startsWith("foo");
}
@Override
public void describeTo(Description description) {
//NOOP
}
}));
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
new FullVerifications(loginService) {};
}
9. Partial Mocking
9.局部嘲弄
9.1. Mockito
9.1.Mockito
Mockito allows partial mocking (a mock that uses the real implementation instead of mocked method calls in some of its methods) in two ways.
Mockito允许以两种方式进行部分嘲讽(一种嘲讽在某些方法中使用真正的实现而不是嘲讽的方法调用)。
You can either use .thenCallRealMethod() in a normal mock method call definition, or you can create a spy instead of a mock in which case the default behavior for that will be to call the real implementation in all non-mocked methods.
你可以在普通的模拟方法调用定义中使用.thenCallRealMethod(),或者你可以创建一个spy而不是一个模拟,在这种情况下,默认行为是在所有非模拟方法中调用真实实现。
@Test
public void partialMocking() {
// use partial mock
loginController.loginService = spiedLoginService;
UserForm userForm = new UserForm();
userForm.username = "foo";
// let service's login use implementation so let's mock DAO call
Mockito.when(loginDao.login(userForm)).thenReturn(1);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
// verify mocked call
Mockito.verify(spiedLoginService).setCurrentUser("foo");
}
9.2. EasyMock
9.2.EasyMock[/strong]
Partial mocking also gets a little more complicated with EasyMock, as you need to define which methods will be mocked when creating the mock.
使用EasyMock,部分嘲讽也变得有点复杂,因为你需要在创建嘲讽时定义哪些方法将被嘲讽。
This is done with EasyMock.partialMockBuilder(Class.class).addMockedMethod(“methodName”).createMock(). Once this is done, you can use the mock as any other non-partial mock.
这是通过EasyMock.partialMockBuilder(Class.class).addMockedMethod(“methodName”).createMock()实现的。一旦完成,你就可以像其他非局部模拟一样使用该模拟。
@Test
public void partialMocking() {
UserForm userForm = new UserForm();
userForm.username = "foo";
// use partial mock
LoginService loginServicePartial = EasyMock.partialMockBuilder(LoginService.class)
.addMockedMethod("setCurrentUser").createMock();
loginServicePartial.setCurrentUser("foo");
// let service's login use implementation so let's mock DAO call
EasyMock.expect(loginDao.login(userForm)).andReturn(1);
loginServicePartial.setLoginDao(loginDao);
loginController.loginService = loginServicePartial;
EasyMock.replay(loginDao);
EasyMock.replay(loginServicePartial);
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
// verify mocked call
EasyMock.verify(loginServicePartial);
EasyMock.verify(loginDao);
}
9.3. JMockit
9.3.JMockit
Partial mocking with JMockit is especially easy. Every method call for which no mocked behavior has been defined in an Expectations(){{}} uses the “real” implementation.
使用JMockit的部分嘲弄特别容易。在Expectations(){{}}中没有定义模拟行为的每个方法调用都使用“真实 “实现。
Now let’s imagine that we want to partially mock the LoginService class to mock the setCurrentUser() method while using the actual implementation of the login() method.
现在让我们设想一下,我们想部分地模拟LoginService类来模拟setCurrentUser()方法,同时使用login()方法的实际实现。
To do this, we first create and pass an instance of LoginService to the expectations block. Then, we only record an expectation for the setCurrentUser() method:
要做到这一点,我们首先创建一个LoginService的实例并传递给期望块。然后,我们只为setCurrentUser()方法记录一个期望。
@Test
public void partialMocking() {
LoginService partialLoginService = new LoginService();
partialLoginService.setLoginDao(loginDao);
loginController.loginService = partialLoginService;
UserForm userForm = new UserForm();
userForm.username = "foo";
new Expectations(partialLoginService) {{
// let's mock DAO call
loginDao.login(userForm); result = 1;
// no expectation for login method so that real implementation is used
// mock setCurrentUser call
partialLoginService.setCurrentUser("foo");
}};
String login = loginController.login(userForm);
Assert.assertEquals("OK", login);
// verify mocked call
new Verifications() {{
partialLoginService.setCurrentUser("foo");
}};
}
10. Conclusion
10.结论
In this post, we’ve been comparing three Java mock libraries, each one with its strong points and downsides.
在这篇文章中,我们一直在比较三个Java模拟库,每一个都有其优点和缺点。
- All three of them are easily configured with annotations to help you define mocks and the object-under-test, with runners to make mock injection as painless as possible.
- We’d say Mockito would win here as it has a special annotation for partial mocks, but JMockit doesn’t even need it, so let’s say that it’s a tie between those two.
- All three of them follow more or less the record-replay-verify pattern, but in our opinion, the best one to do so is JMockit as it forces you to use those in blocks, so tests get more structured.
- Easiness of use is important so you can work as less as possible to define your tests. JMockit will be the chosen option for its fixed-always-the-same structure.
- Mockito is more or less THE most known so that the community will be bigger.
- Having to call replay every time you want to use a mock is a clear no-go, so we’ll put a minus one for EasyMock.
- Consistency/simplicity is also important for me. We loved the way of returning results of JMockit that is the same for “normal” results as for exceptions.
Will all this being said, we’re going to choose JMockit as a kind of a winner even though up till now we’ve been using Mockito as we’ve been captivated by its simplicity and fixed structure and will try and use it from now on.
综上所述,我们将选择JMockit作为一种赢家,尽管到目前为止我们一直在使用Mockito,因为我们已经被它的简单性和固定结构所吸引,并将从现在起尝试使用它。
The full implementation of this tutorial can be found on the GitHub project so feel free to download it and play with it.
本教程的完整实现可以在GitHub项目上找到,所以请随时下载并使用它。