Как работать с org.mockito.exceptions.misusing.MissingMethodInvocationException?

У меня есть класс, который выглядит как

@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);
 }
}

Я хотел протестировать его, издеваясь над любым внешним поведением, поэтому я создал getMemberQueries() используя Pattern-1, описанный в doc

Я, затем напишу свой тест следующим образом

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 весь ClientRegistrationManager, но тот класс, который я хочу проверить, я не могу просто издеваться над этим целым классом.

Я пытаюсь понять мои варианты здесь, я действительно не хочу зависеть от уровня persistence, который будет слишком тяжелым

2 ответа

Seelenvirtuose верна в комментариях: проблема с корнем заключается в том, что вы можете звонить только when mocks и spies и что это, как правило, анти-шаблон, чтобы высмеять класс под тестом.

Один из способов - использовать 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, другой способ сделать это - создать фабрику и заменить ее во время теста. Это особенно хорошо, если вы переходите на завод в качестве необязательного аргумента конструктора, потому что тогда производственные системы могут создавать и передавать свои собственные 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;
 }
 };
 }
}


Один из вариантов - использовать какую-либо фабрику объектов для создания новых объектов в тестируемых классах, а затем издеваться над этим заводом.

protected MemberQueries getMemberQueries() {
 return ObjectFactory.newMemberQueries(crudService);
}

Если по какой-то причине вы хотите высмеять только выбранные методы какого-либо объекта, а не весь объект, вы можете сделать это с помощью Mockito. Проверьте этот пример.

licensed under cc by-sa 3.0 with attribution.