我有一个类似于
的课程@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class ClientRegistrationManager {
private CrudService crudService;
private UniqueIdGenerator uniqueIdGenerator;
@SuppressWarnings("UnusedDeclaration")
public ClientRegistrationManager() {
}
@Inject
public ClientRegistrationManager(@Nonnull final CrudService crudService, @Nonnull final UniqueIdGenerator uniqueIdGenerator) {
this.crudService = crudService;
this.uniqueIdGenerator = uniqueIdGenerator;
}
public Member register(@Nonnull final String email, @Nonnull final String userExternalId, @Nonnull final String password) {
final Member existingMember;
try {
existingMember = getMemberQueries().getMemberByEmail(email);
} catch (final NoResultException e) {
return createNewMemberAndGetClientDetail(email, userExternalId);
}
return existingMember;
}
...
@Nonnull
protected MemberQueries getMemberQueries() {
return new MemberQueries(crudService);
}
}
我想通过模拟任何外部行为来测试它,所以我使用doc中描述的Pattern - 1创建了getMemberQueries()
我,然后按以下方式编写我的测试
public class ClientRegistrationManagerTest {
@Mock
private MemberQueries memberQueries;
@Mock
private CrudService crudService;
@Mock
private UniqueIdGenerator uniqueIdGenerator;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testMockedMemberQueries() {
final ClientRegistrationManager clientRegistrationManager = new ClientRegistrationManager(crudService, uniqueIdGenerator);
Mockito.when(clientRegistrationManager.getMemberQueries()).thenReturn(memberQueries);
clientRegistrationManager.getMemberQueries();
assertTrue(true);
}
}
当我运行时,我收到错误
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
2. inside when() you don't call method on mock but on some other object.
3. the parent of the mocked class is not public.
It is a limitation of the mock engine.
现在意味着我需要模拟整个ClientRegistrationManager
,但是我要测试的类,我不能只是模拟整个类。
我想在这里理解我的选择,我真的不想依赖persistence
图层,这将太重了
答案 0 :(得分:3)
Seelenvirtuose在评论中是正确的:根本问题是你只能在模拟和间谍上调用when
,并且它通常是模仿被测试类的反模式
一种技巧是使用spy
,它可以拦截和验证对真实对象的调用("部分模拟"):
final ClientRegistrationManager clientRegistrationManager =
Mockito.spy(new ClientRegistrationManager(crudService, uniqueIdGenerator);
// doReturn is important because the call to contains a call to the mock
// before Mockito has intercepted it. In your case, this may just create a useless
// real MemberQueries, but in other cases it can throw an exception.
Mockito.doReturn(memberQueries).when(clientRegistrationManager).getMemberQueries();
作为Predrag Magic wrote,另一种方法是创建工厂并在测试期间替换它。如果你将工厂作为可选的构造函数参数传入,这是特别好的,因为生产系统可以创建并传入他们自己的MemberQueryFactory,你的类仍然可以按预期工作。
public class ClientRegistrationManager {
static class MemberQueriesFactory {
MemberQueries getMemberQueries() {
return ObjectFactory.newMemberQueries(crudService);
}
}
/** Package-private. Visible for unit testing. */
MemberQueriesFactory memberQueriesFactory = new MembersQueryFactory();
}
@RunWith(MockitoJUnitRunner.class)
public class ClientRegistrationManagerTest {
@Mock ClientRegistrationManager.MembersQueryFactory mockMembersQueryFactory;
private ClientRegistrationManager getManagerForTest() {
ClientRegistrationManager manager = new ClientRegistrationManager();
manager.memberQueriesFactory = mockMembersQueryFactory;
return manager;
}
}
我最喜欢的方法是跳过Mockito并直接覆盖测试中的测试类:
@RunWith(MockitoJUnitRunner.class)
public class ClientRegistrationManagerTest {
@Mock MemberQueries memberQueries;
private ClientRegistrationManager getManagerForTest() {
ClientRegistrationManager manager = new ClientRegistrationManager() {
@Override void getMemberQueries() {
return memberQueries;
}
};
}
}
答案 1 :(得分:0)
一种选择是使用某种对象工厂在被测试的类中创建新对象,然后模拟该工厂。
protected MemberQueries getMemberQueries() {
return ObjectFactory.newMemberQueries(crudService);
}
如果由于某种原因,你想要模拟一些对象的选定方法而不是整个对象,你可以用Mockito来做。查看this示例。