Java 如何处理org.mockito.exceptions.misusing.MissingMethodInvocationException?

Java 如何处理org.mockito.exceptions.misusing.MissingMethodInvocationException?,java,unit-testing,mocking,mockito,Java,Unit Testing,Mocking,Mockito,我有一门课看起来像 @Stateless @TransactionAttribute(TransactionAttributeType.REQUIRED) public class ClientRegistrationManager { private CrudService crudService; private UniqueIdGenerator uniqueIdGenerator; @SuppressWarnings("UnusedDeclaration")

我有一门课看起来像

@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);
    }
}
我想通过模拟任何外部行为来测试它,所以我使用中描述的模式-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
,但是我要测试的类,我不能只是模拟整个类


我试图理解我在这里的选择,我真的不想依赖于
持久性
层,这将太重

一个选择是使用某种对象工厂在测试的类中创建新对象,然后模拟该工厂

protected MemberQueries getMemberQueries() {
    return ObjectFactory.newMemberQueries(crudService);
}
如果出于某种原因,您只想模拟某个对象的选定方法,而不是整个对象,那么可以使用Mockito进行模拟。查看示例。

在注释中是正确的:根本问题是,您只能在模拟和间谍时调用
,并且模拟被测试的类通常是一种反模式

一种技术是使用
spy
,它允许您拦截和验证对真实对象的调用(“部分模拟”):

同样,另一种方法是创建一个工厂,并在测试期间替换它。如果您将工厂作为可选构造函数参数传入,这尤其好,因为这样生产系统就可以创建并传入自己的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;
      }
    };
  }
}

好的,您自己正在创建一个
ClientRegistrationManager
的实例,因此它不是mock,但在
时将其传递给
。我认为,这条信息非常清楚:“when()需要一个参数,该参数必须是‘模拟的方法调用’”。应该只为被测试类的依赖项创建mock。是的@Seelenvirtuose,我理解这一点。我正在寻找如何模拟在测试类内部创建的
新对象()
的想法这归结为一个问题:为什么要模拟测试类?我不想这样做,我想模拟测试类的
新对象()
创建。。。我不明白。也许更大的图景在这里是必要的。你能再描述一下吗?事实上,永远不应该有必要模拟被测试的类,也不应该有必要对被测试类的模拟实例的方法调用设置结果(就像您所做的那样)。我喜欢spy的想法,因为它们不太侵入性,而且我不必编写太多代码来测试
@RunWith(MockitoJUnitRunner.class)
public class ClientRegistrationManagerTest {
  @Mock MemberQueries memberQueries;

  private ClientRegistrationManager getManagerForTest() {
    ClientRegistrationManager manager = new ClientRegistrationManager() {
      @Override void getMemberQueries() {
        return memberQueries;
      }
    };
  }
}