Насмешка над аргументами, передаваемыми обратным вызовам (лямбда-выражениям) - PullRequest
0 голосов
/ 26 июня 2018

Как бы я смоделировал методы, которые принимают лямбду, используя Mockito, чтобы я мог контролировать, какие аргументы передаются в обратный вызов? Я специально пытаюсь издеваться над JDBI методом useExtension, который используется так:

jdbi.useExtension(OrgUnitDao.class, dao -> {
    // Skip if already loaded
    // Skip if already loaded
    if (dao.orgUnitsAreLoaded()) {

Я хотел бы заменить объект dao, переданный обратно в обратный вызов, чтобы я мог управлять ветвлением, используя возвращаемое значение dao.orgUnitsAreLoaded().

Подпись выглядит так

public <E,X extends Exception> void useExtension(Class<E> extensionType,
                                             ExtensionConsumer<E,X> callback)
                                      throws NoSuchExtensionException,
                                             X extends Exception

Ответы [ 2 ]

0 голосов
/ 27 июня 2018

Для параллельного разговора по вопросу "Вызов обратных вызовов с помощью Mockito" , ваша лямбда может быть вызвана синхронно во время выполнения тестируемого вами метода, или она может быть вызвана позже на основе некоторого внешнего фактор или взаимодействие. Как и ответ Дауда там , ваш ответ здесь с использованием ответа Mockito будет работать, и это единственный путь, если вы ищете синхронный стиль (где mockJdbi вызывает вашу лямбду непосредственно перед возвращением methodUnderTest). Если ваши лямбды будут вызываться асинхронно или если ваша система допускает асинхронный вызов лямбды, вы можете захотеть проверить состояние после того, как тестируемый метод вернет , но , прежде чем взаимодействовать с лямбда .

// MockitoJUnitRunner, MockitoRule, or MockitoAnnotations.initMocks populate these.
// Especially useful for the ArgumentCaptor's generic arguments.
@Mock Jdbi mockJdbi;
@Mock OrgUnitDao mockOrgUnitDao;
@Captor ArgumentCaptor<ExtensionConsumer<OrgUnitDao, RuntimeException>>
    extensionConsumerCaptor;

@Test public void yourTest() throws Exception {
  // No stubbing needed! Just create the system under test.
  YourSystemUnderTest systemUnderTest = new YourSystemUnderTest(mockJdbi);

  // Call the method under test, which presumably calls useExtension(...).
  systemUnderTest.methodUnderTest();

  // Assert anything that should be true before the lambda is called.
  assertFalse(systemUnderTest.getSomeState());

  // Confirm that useExtension was called, and simultaneously acquire the lambda.
  // ArgumentCaptor.capture() is a matcher, so every argument requires a matcher like eq.
  verify(mockJdbi).useExtension(eq(OrgUnitDao.class), extensionConsumerCaptor.capture());

  // Prepare the mock DAO and call the lambda.
  when(mockDao.getFoo()).thenReturn("bar");
  extensionConsumerCaptor.getValue().useExtension(mockDao);

  // Assert anything that should be true after the lambda is called.
  assertTrue(systemUnderTest.getSomeState());
}

Хотя лямбды уменьшают шаблон, ранее связанный с анонимными внутренними классами, вы также можете предпочесть использование стиля Captor, поскольку он избавляет вас от создания длинных реализаций ответа и скрытия в них ваших тестовых утверждений или проверок Mockito. Это особенно заманчиво, если ваш проект предпочитает макеты в стиле BDD с четкой структурой «дан-когда-тогда» (хотя мой пример более похож на «дан-когда-тогда-тогда-тогда»).

0 голосов
/ 26 июня 2018

Это полный ответ на мой вопрос. Он упрощен до самых основ того, как выполнять заглушки, и поэтому не отражает производственный код, который я должен тестировать, но он показывает именно механику, необходимую для этого.

final Jdbi jdbi = mock(Jdbi.class);
doAnswer(invocation -> {

    System.out.println("this is the doAnswer lambda - just setting up the answer and the mocks");

    final Class<OrgUnitDao> daoClass = invocation.getArgument(0);
    final ExtensionConsumer callback = invocation.getArgument(1);
    final OrgUnitDao mock1 = mock(daoClass);

    when(mock1.orgUnitsAreLoaded()).thenReturn(false);

    // call the actual callback method
    callback.useExtension(mock1);

    return null;

}).when(jdbi).useExtension(eq(OrgUnitDao.class), any());

// This is the method call I am to test
// Regard this as hidden away in some outer method in
// the System-Under-Test, but that I have been able
// to inject all its dependencies
jdbi.useExtension(OrgUnitDao.class, new Foo());

/// Further down, outside of the method

// Only replaced the lambda with this to get toString() for debugging ...
class Foo implements ExtensionConsumer<OrgUnitDao, RuntimeException> {
    @Override
    public void useExtension(OrgUnitDao orgUnitDao) throws RuntimeException {
        System.out.println("A real method call, now using the passed in mocked dao:" + orgUnitDao.orgUnitsAreLoaded());
    }

    @Override
    public String toString() {
        return "OrgUnitDao class";
    }
}
...