模拟 Vertx.io 异步处理程序
Mocking a Vertx.io async handler
当我同步时,我编写了模拟持久性部分的单元测试并检查调用者的行为。这是我通常做的一个例子:
@Mock
private OfferPersistenceServiceImpl persistenceService;
@Inject
@InjectMocks
private OfferServiceImpl offerService;
...
@Test
public void createInvalidOffer() {
offer = new Offer(null, null, null, null, null, 4, 200D, 90D);
String expectedMessage = Offer.class.getName() + " is not valid: " + offer.toString();
Mockito.when(persistenceService.create(offer)).thenThrow(new IllegalArgumentException(expectedMessage));
Response response = offerService.create(offer);
Mockito.verify(persistenceService, Mockito.times(1)).create(offer);
Assert.assertEquals(INVALID_INPUT, response.getStatus());
String actualMessage = response.getEntity().toString();
Assert.assertEquals(expectedMessage, actualMessage);
}
但现在我爱上了 Vertx.io(我对它还很陌生)并且我想异步。好的。但是 Vertx 有处理程序,因此要模拟的新持久性组件如下所示:
...
mongoClient.insert(COLLECTION, offer, h-> {
...
});
所以我在猜测如何模拟处理程序 h
来测试 class 谁在使用那个 mongoClient
或者即使它是测试 Vertx.io 的正确方法。我正在使用 vertx.io 3.5.0
、junit 4.12
和 mockito 2.13.0
。谢谢。
更新
我试图遵循 tsegimond 的建议,但我无法理解 Mockito 的 Answer
和 ArgumentCaptor
如何帮助我。到目前为止,这是我尝试过的。
使用 ArgumentCaptor
:
JsonObject offer = Mockito.mock(JsonObject.class);
Mockito.when(msg.body()).thenReturn(offer);
Mockito.doNothing().when(offerMongo).validate(offer);
RuntimeException rex = new RuntimeException("some message");
...
ArgumentCaptor<Handler<AsyncResult<String>>> handlerCaptor =
ArgumentCaptor.forClass(Handler.class);
ArgumentCaptor<AsyncResult<String>> asyncResultCaptor =
ArgumentCaptor.forClass(AsyncResult.class);
offerMongo.create(msg);
Mockito.verify(mongoClient,
Mockito.times(1)).insert(Mockito.anyString(), Mockito.any(), handlerCaptor.capture());
Mockito.verify(handlerCaptor.getValue(),
Mockito.times(1)).handle(asyncResultCaptor.capture());
Mockito.when(asyncResultCaptor.getValue().succeeded()).thenReturn(false);
Mockito.when(asyncResultCaptor.getValue().cause()).thenReturn(rex);
Assert.assertEquals(Json.encode(rex), msg.body().encode());
并使用 Answer
:
ArgumentCaptor<AsyncResult<String>> handlerCaptor =
ArgumentCaptor.forClass(AsyncResult.class);
AsyncResult<String> result = Mockito.mock(AsyncResult.class);
Mockito.when(result.succeeded()).thenReturn(true);
Mockito.when(result.cause()).thenReturn(rex);
Mockito.doAnswer(new Answer<MongoClient>() {
@Override
public MongoClient answer(InvocationOnMock invocation) throws Throwable {
((Handler<AsyncResult<String>>)
invocation.getArguments()[2]).handle(handlerCaptor.capture());
return null;
}
}).when(mongoClient).insert(Mockito.anyString(), Mockito.any(),
Mockito.any());
userMongo.create(msg);
Assert.assertEquals(Json.encode(rex), msg.body().encode());
现在我很困惑。有没有办法模拟 AsyncResult
让它在 succeed()
上 return false?
终于我有时间调查了,我做到了。这是我的解决方案。
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(VertxUnitRunner.class)
@PrepareForTest({ MongoClient.class })
public class PersistenceTest {
private MongoClient mongo;
private Vertx vertx;
@Before
public void initSingleTest(TestContext ctx) throws Exception {
vertx = Vertx.vertx();
mongo = Mockito.mock(MongoClient.class);
PowerMockito.mockStatic(MongoClient.class);
PowerMockito.when(MongoClient.createShared(Mockito.any(), Mockito.any())).thenReturn(mongo);
vertx.deployVerticle(Persistence.class, new DeploymentOptions(), ctx.asyncAssertSuccess());
}
@SuppressWarnings("unchecked")
@Test
public void loadSomeDocs(TestContext ctx) {
Doc expected = new Doc();
expected.setName("report");
expected.setPreview("loremipsum");
Message<JsonObject> msg = Mockito.mock(Message.class);
Mockito.when(msg.body()).thenReturn(JsonObject.mapFrom(expected));
JsonObject result = new JsonObject().put("name", "report").put("preview", "loremipsum");
AsyncResult<JsonObject> asyncResult = Mockito.mock(AsyncResult.class);
Mockito.when(asyncResult.succeeded()).thenReturn(true);
Mockito.when(asyncResult.result()).thenReturn(result);
Mockito.doAnswer(new Answer<AsyncResult<JsonObject>>() {
@Override
public AsyncResult<JsonObject> answer(InvocationOnMock arg0) throws Throwable {
((Handler<AsyncResult<JsonObject>>) arg0.getArgument(3)).handle(asyncResult);
return null;
}
}).when(mongo).findOne(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
Async async = ctx.async();
vertx.eventBus().send("persistence", new JsonObject(), msgh -> {
if (msgh.failed()) {
System.out.println(msgh.cause().getMessage());
}
ctx.assertTrue(msgh.succeeded());
ctx.assertEquals(expected, Json.decodeValue(msgh.result().body().toString(), Doc.class));
async.complete();
});
async.await();
}
}
使用 Powemockito
模拟 MongoClient.createShared
静态方法,这样当 Verticle 启动时你就会有你的模拟。模拟异步处理程序需要编写一些代码。如您所见,模拟从 Message<JsonObject> msg = Mockito.mock(Message.class);
开始,在 Mockito.doAnswer(new Answer...
结束。在 Answer
的方法中选择处理程序参数并强制它处理您的异步结果,然后您就完成了。
通常情况下,我会对 post 使用注释,但格式会丢失。公认的解决方案效果很好,请注意可以使用 Java 8+ 稍微简化它,并且您可以使用实际对象而不是 JSON.
doAnswer((Answer<AsyncResult<List<Sample>>>) arguments -> {
((Handler<AsyncResult<List<Sample>>>) arguments.getArgument(1)).handle(asyncResult);
return null;
}).when(sampleService).findSamplesBySampleFilter(any(), any());
getArgument(1),指的是方法中处理程序参数的索引,例如:
@Fluent
@Nonnull
SampleService findSamplesBySampleFilter(@Nonnull final SampleFilter sampleFilter,
@Nonnull final Handler<AsyncResult<List<Sample>>> resultHandler);
当我同步时,我编写了模拟持久性部分的单元测试并检查调用者的行为。这是我通常做的一个例子:
@Mock
private OfferPersistenceServiceImpl persistenceService;
@Inject
@InjectMocks
private OfferServiceImpl offerService;
...
@Test
public void createInvalidOffer() {
offer = new Offer(null, null, null, null, null, 4, 200D, 90D);
String expectedMessage = Offer.class.getName() + " is not valid: " + offer.toString();
Mockito.when(persistenceService.create(offer)).thenThrow(new IllegalArgumentException(expectedMessage));
Response response = offerService.create(offer);
Mockito.verify(persistenceService, Mockito.times(1)).create(offer);
Assert.assertEquals(INVALID_INPUT, response.getStatus());
String actualMessage = response.getEntity().toString();
Assert.assertEquals(expectedMessage, actualMessage);
}
但现在我爱上了 Vertx.io(我对它还很陌生)并且我想异步。好的。但是 Vertx 有处理程序,因此要模拟的新持久性组件如下所示:
...
mongoClient.insert(COLLECTION, offer, h-> {
...
});
所以我在猜测如何模拟处理程序 h
来测试 class 谁在使用那个 mongoClient
或者即使它是测试 Vertx.io 的正确方法。我正在使用 vertx.io 3.5.0
、junit 4.12
和 mockito 2.13.0
。谢谢。
更新
我试图遵循 tsegimond 的建议,但我无法理解 Mockito 的 Answer
和 ArgumentCaptor
如何帮助我。到目前为止,这是我尝试过的。
使用 ArgumentCaptor
:
JsonObject offer = Mockito.mock(JsonObject.class);
Mockito.when(msg.body()).thenReturn(offer);
Mockito.doNothing().when(offerMongo).validate(offer);
RuntimeException rex = new RuntimeException("some message");
...
ArgumentCaptor<Handler<AsyncResult<String>>> handlerCaptor =
ArgumentCaptor.forClass(Handler.class);
ArgumentCaptor<AsyncResult<String>> asyncResultCaptor =
ArgumentCaptor.forClass(AsyncResult.class);
offerMongo.create(msg);
Mockito.verify(mongoClient,
Mockito.times(1)).insert(Mockito.anyString(), Mockito.any(), handlerCaptor.capture());
Mockito.verify(handlerCaptor.getValue(),
Mockito.times(1)).handle(asyncResultCaptor.capture());
Mockito.when(asyncResultCaptor.getValue().succeeded()).thenReturn(false);
Mockito.when(asyncResultCaptor.getValue().cause()).thenReturn(rex);
Assert.assertEquals(Json.encode(rex), msg.body().encode());
并使用 Answer
:
ArgumentCaptor<AsyncResult<String>> handlerCaptor =
ArgumentCaptor.forClass(AsyncResult.class);
AsyncResult<String> result = Mockito.mock(AsyncResult.class);
Mockito.when(result.succeeded()).thenReturn(true);
Mockito.when(result.cause()).thenReturn(rex);
Mockito.doAnswer(new Answer<MongoClient>() {
@Override
public MongoClient answer(InvocationOnMock invocation) throws Throwable {
((Handler<AsyncResult<String>>)
invocation.getArguments()[2]).handle(handlerCaptor.capture());
return null;
}
}).when(mongoClient).insert(Mockito.anyString(), Mockito.any(),
Mockito.any());
userMongo.create(msg);
Assert.assertEquals(Json.encode(rex), msg.body().encode());
现在我很困惑。有没有办法模拟 AsyncResult
让它在 succeed()
上 return false?
终于我有时间调查了,我做到了。这是我的解决方案。
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(VertxUnitRunner.class)
@PrepareForTest({ MongoClient.class })
public class PersistenceTest {
private MongoClient mongo;
private Vertx vertx;
@Before
public void initSingleTest(TestContext ctx) throws Exception {
vertx = Vertx.vertx();
mongo = Mockito.mock(MongoClient.class);
PowerMockito.mockStatic(MongoClient.class);
PowerMockito.when(MongoClient.createShared(Mockito.any(), Mockito.any())).thenReturn(mongo);
vertx.deployVerticle(Persistence.class, new DeploymentOptions(), ctx.asyncAssertSuccess());
}
@SuppressWarnings("unchecked")
@Test
public void loadSomeDocs(TestContext ctx) {
Doc expected = new Doc();
expected.setName("report");
expected.setPreview("loremipsum");
Message<JsonObject> msg = Mockito.mock(Message.class);
Mockito.when(msg.body()).thenReturn(JsonObject.mapFrom(expected));
JsonObject result = new JsonObject().put("name", "report").put("preview", "loremipsum");
AsyncResult<JsonObject> asyncResult = Mockito.mock(AsyncResult.class);
Mockito.when(asyncResult.succeeded()).thenReturn(true);
Mockito.when(asyncResult.result()).thenReturn(result);
Mockito.doAnswer(new Answer<AsyncResult<JsonObject>>() {
@Override
public AsyncResult<JsonObject> answer(InvocationOnMock arg0) throws Throwable {
((Handler<AsyncResult<JsonObject>>) arg0.getArgument(3)).handle(asyncResult);
return null;
}
}).when(mongo).findOne(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
Async async = ctx.async();
vertx.eventBus().send("persistence", new JsonObject(), msgh -> {
if (msgh.failed()) {
System.out.println(msgh.cause().getMessage());
}
ctx.assertTrue(msgh.succeeded());
ctx.assertEquals(expected, Json.decodeValue(msgh.result().body().toString(), Doc.class));
async.complete();
});
async.await();
}
}
使用 Powemockito
模拟 MongoClient.createShared
静态方法,这样当 Verticle 启动时你就会有你的模拟。模拟异步处理程序需要编写一些代码。如您所见,模拟从 Message<JsonObject> msg = Mockito.mock(Message.class);
开始,在 Mockito.doAnswer(new Answer...
结束。在 Answer
的方法中选择处理程序参数并强制它处理您的异步结果,然后您就完成了。
通常情况下,我会对 post 使用注释,但格式会丢失。公认的解决方案效果很好,请注意可以使用 Java 8+ 稍微简化它,并且您可以使用实际对象而不是 JSON.
doAnswer((Answer<AsyncResult<List<Sample>>>) arguments -> {
((Handler<AsyncResult<List<Sample>>>) arguments.getArgument(1)).handle(asyncResult);
return null;
}).when(sampleService).findSamplesBySampleFilter(any(), any());
getArgument(1),指的是方法中处理程序参数的索引,例如:
@Fluent
@Nonnull
SampleService findSamplesBySampleFilter(@Nonnull final SampleFilter sampleFilter,
@Nonnull final Handler<AsyncResult<List<Sample>>> resultHandler);