Unit testing Mockito:存根函数不工作

Unit testing Mockito:存根函数不工作,unit-testing,junit,mockito,junit4,Unit Testing,Junit,Mockito,Junit4,我正在使用编写一个简单的单元测试 然后,测试一个函数: public class MyService { public void getData() { executor.execute(new MyRunnable() { @Override doTask() { MyRestClient client = getRestClient(); Response resp = client.getFromServ

我正在使用编写一个简单的单元测试

然后,测试一个函数:

public class MyService {
  public void getData() {
     executor.execute(new MyRunnable() {
        @Override
        doTask() {
          MyRestClient client = getRestClient();
          Response resp = client.getFromServer();
          persist(resp.getData());
        }
     });
  }
}

protected MyRestClient getRestClient() {
    return new MyRestClient();
}
我的测试用例,我想测试
doTask()
是否已运行&
resp.getData()
是否已持久化:

@Test
public void testGetData() {
   MyService spyService = spy(MyService.getInstance());

   // mock client
   MyRestClient mockedClient = mock(MyRestClient.class);
   mockedClient.setData("testData");

   // stub getRestClient() function to return mocked client
   when(spyService.getRestClient()).thenReturn(mockedClient);

   // SUT
   spyService.getData();

   // run the Runnable task.
   Mockito.doAnswer(new Answer<Object>() {
        public Object answer(InvocationOnMock invocation) throws Exception {
            Object[] args = invocation.getArguments();
            Runnable runnable = (Runnable) args[0];
            runnable.doTask();
            return null;
        }
    }).when(executor).execute(Mockito.any(Runnable.class));
    ...
}
@测试
public void testGetData(){
MyService spyService=spy(MyService.getInstance());
//模拟客户端
MyRestClient mockedClient=mock(MyRestClient.class);
mockedClient.setData(“testData”);
//用于返回模拟客户端的stub getRestClient()函数
when(spyService.getRestClient())。然后返回(mockedClient);
//苏特
spyService.getData();
//运行可运行任务。
Mockito.doAnswer(新答案(){
公共对象应答(InvocationMock调用)引发异常{
对象[]args=invocation.getArguments();
Runnable Runnable=(Runnable)args[0];
runnable.doTask();
返回null;
}
}).when(executor).execute(Mockito.any(Runnable.class));
...
}
如上所述,我对
getRestClient()
函数进行存根,以返回一个模拟的
MyRestClient
。但是,当运行测试用例时,它不会存根
getRestClient()
,而是运行真正的函数。为什么?

[编辑]以下评论和评论反馈

经验法则是不要模仿被测试的类。此外,如果您的被测类不使用
new
关键字,那么您的测试将更加容易。而是使用
Factory
类来创建对象。无需使用
Mockito.spy()
Mockito.mock()

下面的答案需要重要的测试设置,这一事实告诉您,
MyService
有太多的责任,需要简化。然而,为了直接回答您的问题,这里是如何重构代码以支持使用mock验证对
persist()
的调用

MyService
在构造函数中接受您将在测试设置中模拟的对象。将它们传递到构造函数中允许您的JUnit测试用例创建模拟,并保留对它们的引用以供稍后验证

public class MyService {

  private MyRunnableFactory runFactory;
  private MyRestClientFactory restFactory;
  private MyRestDao dao;

  // inject constructor arguments
  public MyService(MyRunnableFactory runFactory, MyRestClientFactory restFactory, MyRestDao dao) {
    this.runFactory = runFactory;
    this.restFactory = restFactory;
    this.dao = dao;
  }

  public void getData() {
     MyRestClient restClient = restFactory.createInstance();
     MyRunnable runner = runFactory.createInstance(restClient, dao);
     executor.execute(runner);
  }
}
MyRunnable
被创建,以便在需要时可以对其进行隔离测试。我们再次将模拟对象注入构造函数。正如您在问题中所写的那样,很容易内联可运行项,但是您失去了控制在测试中创建的
实例的能力

public class MyRunnable implements Runnable {

  private MyRestClient restClient;
  private MyRestDao dao;

  public MyRunnable(MyRestClient restClient, MyRestDao dao) {
    this.restClient = restClient;
    this.dao = dao;
  }

  public void run() {
     Response resp = restClient.getFromServer();
     dao.persist(resp.getData());
  }
}
MyRestDao
是创建的,因为这是您要在测试用例中验证的类。我看不出您的问题中在哪里定义了persist(),所以我们创建了一个数据访问对象(DAO)来实现它

public class MyRestDao {

  public void persist() {
     // save to some repository
  }
}
现在,让我们编写使用上述类的测试用例。我们要验证是否已调用persist()方法

@RunWith(MockitoJUnitRunner.class)
public class MyServiceTest {    

    @Mock MyRestDao dao;
    @Mock MyRestClient restClient;
    @Mock MyRunnableFactory runFactory;
    @Mock MyRestClientFactory restFactory;

    @Test
    public void testPersistIsCalled() {

       Response expectedResponse = new Response("some data");   // real implementation, not mocked
       MyRunnable runner = new MyRunnable(restClient, dao);     // real implementation, not mocked

       when(restFactory.createInstance()).thenReturn(restClient);
       when(runFactory.createInstance(restClient, dao)).thenReturn(runner);
       when(restClient.getFromServer()).thenReturn(expectedResponse);
       when(restClient.getData()).thenReturn(myRunnable);

       // method under test
       MyService service = new MyService(runFactory, restFactory);
       service.getData();

       verify(dao).persist(expectedResponse.getData());
    }
}

注意,这个测试用例是脆弱的,因为它与
MyService
类的实际实现紧密耦合。理想情况下,您需要的测试不需要了解被测类的内部工作情况。

旁白:不使用
doAnswer
,您可以使用获取
Runnable
。为什么您试图在mock对象
mockedClient.setData(“testData”)上设置一个值?这一行实际上什么都不做。您也没有模拟
getFromServer()
方法,因此您的示例代码可能缺少某些内容。我无法重现该问题。对我来说,在模拟的
RestClient
上调用
getFromServer()。你怎么知道
getRestClient()
没有存根,我在你的代码中没有看到。