使用回调和SynchronousQueue跟踪结果的单元测试方法



我正在使用Mockito测试一个方法,该方法在内部进行网络调用,并根据网络调用的结果返回一个值。此方法使用SynchronousQueue等待结果,结果由网络调用的回调设置:

HelperClass helperClassObject = new HelperClassObject();
...
public SomeResultCode methodWithNetworkCall() {
SynchronousQueue<SomeResultCode> resultQueue = new SynchronousQueue<>();
// some condition checking code
helperClassObject.makeNetworkCall(new GenericCallback() {
@Override
public void onSuccess(JSONObject response) {
resultQueue.offer(SomeResultCode.SUCCESS);
}
@Override
public void onFailure(VolleyError error) {
resultQueue.offer(SomeResultCode.FAILURE);
}
});
SomeResultCode resultCode = null;
try {
resultCode = resultQueue.poll(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
}
return resultCode == null ? SomeResultCode.FAILURE : resultCode;
}

在我的一个单元测试方法中,我试图验证成功的网络调用是否返回SUCCESS。我尝试过使用ArgumentCaptor和doAnswer来触发回调的onSuccess。但是,该方法返回FAILURE。我在onSuccess中放置了一个断点,当我使用ArgumentCaptor的方式时,看起来在轮询超时后会触发onSuccess。当我使用doAnswer方法时,我会看到在设置过程中调用了onSuccess(doAnswer.When),但在我实际调用该方法之后没有调用。我做错了什么?

EDIT再次遍历代码,看起来答案是从我正在测试的方法中调用的(即,当我在测试期间调用testObject.methodWithNetworkCall时),而不是在设置期间。因此,它正在做它应该做的事情:用onSuccess来回应。但它的回应是在投票前成功。因此,问题似乎不在于答案和嘲讽通常不起作用/设置错误,而是SynchronousQueue测试的问题。

这是我的测试代码:

public class TestClassUnitTest {
TestClass sut;
HelperClass helperClassObject = mock(HelperClass.class);
@Before
public void setup() {
sut = new TestClass();
injectField(sut, "helperClassFieldName", helperClassObject);
}
public void injectField(Object testObject, String fieldName, T mockToInject) {
// some code using reflection to inject the mock object into the test object
}
@Test
public void testMethodWithNetworkCallWithCaptor() {
ArgumentCaptor<GenericCallback> captor = ArgumentCaptor.forClass(GenericCallback.class);
SomeResultCode result = sut.methodWithNetworkcall();
verify(helperClassObject, times(1)).makeNetworkCall(captor.capture());
captor.getValue().onSuccess(new JSONObject());
Assert.assertEquals(SomeResultCode.SUCCESS, result);
}
@Test
public void testMethodWithNetworkCallWithDoAnswer() {
doAnswer(new Answer(){
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((GenericCallback)invocation.getArguments()[0]).onSuccess(new JSONObject());
return null;
}
}).when(helperClassObject).makeNetworkCall(any(GenericCallback.class));
SomeResultCode result = sut.methodWithNetworkcall();
Assert.assertEquals(SomeResultCode.SUCCESS, result);
}
}

看起来您没有在测试中的系统中替换HelperClassObject,或者至少您没有向我们展示您的位置。Mockito.mock(或@Mockspy@Spy)返回的mock并不适用于传入的类的每个实例;它只创建一个实例。您必须确保在测试中的系统中设置实例(此处为HelperClassObject),可能是将其作为构造函数参数传入,将实例设置为字段,或使用setter方法进行设置。如果您像您向我们展示的那样将其保留为new HelperClassObject(),Mockito将无法帮助您。

您提到的"onSuccess called during the setup(doAnswer.when)"让我有点担心,因为如果您使用Mockito.mock创建了一个mock,那么Mockito就没有理由在安装过程中实际调用您的Answer。这让我相信,您的HelperClassObject或makeNetworkcall方法不能被嘲笑,可能是因为可见性有限,或者因为它们被标记为staticfinal。Mockito通过编写您正在嘲笑的类的自定义子类来有效地工作,因此请确保您正在模仿的类和方法是public和非final,以确保它们是可重写的。(可以模拟protected或封装私有方法,但某些版本的Mockito在某些代码结构方面存在复杂性。让我们先排除这种可能性。)

在您确保该类是可模拟的,并且它正在使用您传入的模拟HelperClassObject实例之后,您将能够继续前进。你会想要追求doAnswer结构:ArgumentCaptor版本不起作用,因为如果你的methodWithNetworkcall阻塞并等待结果,那么在你有机会调用verify并回调之前,你会得到一个FAILURE返回值。(这解释了超时的原因。)在其他情况下,在测试中的方法可以首先返回,ArgumentCaptor解决方案对您来说更实用。

在这种情况下,使用doAnswer是正确的方法。问题在于SynchronousQueue的工作方式:它期望多线程使用这个队列:

一个阻塞队列,其中每个插入操作都必须等待另一个线程执行相应的移除操作,反之亦然。

但是在这个测试用例中,测试在单个线程上运行。

解决方案:模拟SynchronousQueue,使用doAnswer得到offer()poll()将结果推送/弹出到LinkedList上。在此过程中,我还将SynchrnousQueue局部变量resultQueuemethodWithNetworkCall()中移出,并使其成为实例成员。更新的测试代码如下:

public class TestClassUnitTest {
TestClass sut;
private LinkedList testQueue = new LinkedList();
private SynchronousQueue<SomeResultCode> resultQueueMock = mock(SynchronousQueue.class);
private HelperClass helperClassMock = mock(HelperClass.class);
@Before
public void setup() {
sut = new TestClass();
injectField(sut, "resultQueue", resultQueueMock);
injectField(sut, "helperClassFieldName", helperClassMock);
}
public void injectField(Object testObject, String fieldName, T mockToInject) {
// some code using reflection to inject the mock object into the test object
}
@Test
public void testMethodWithNetworkCallWithDoAnswer() {
doAnswer(new Answer(){
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((GenericCallback)invocation.getArguments()[0]).onSuccess(new JSONObject());
return null;
}
}).when(helperClassMock).makeNetworkCall(any(GenericCallback.class));
mockQueue();
SomeResultCode result = sut.methodWithNetworkCall();
Assert.assertEquals(SomeResultCode.SUCCESS, result);
}
private void mockQueue() {
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
testQueue.push(((SchedulableJob.Result)invocation.getArguments()[0]));
return true;
}
}).when(resultQueueMock).offer(any());
try {
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
if (testQueue.size() > 0) {
return testQueue.pop();
} else {
return null;
}
}
}).when(resultQueueMock).poll(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
}
}
}

相关内容

  • 没有找到相关文章

最新更新