Как издеваться над Java-клиентом riak?

Я пытаюсь выполнить модульный тест, который использует com.basho.riak:riak-client:2.0.0. Я издевался над всеми клиентскими классами riak и надеялся получить бесполезный, но работающий тест. Однако это не удается с нулевым указателем:

java.lang.NullPointerException
  at com.basho.riak.client.api.commands.kv.KvResponseBase.convertValues(KvResponseBase.java:243)
  at com.basho.riak.client.api.commands.kv.KvResponseBase.getValue(KvResponseBase.java:150)
  at com.basho.riak.client.api.commands.kv.FetchValue$Response.getValue(FetchValue.java:171)

Мой тест выглядит так:

    @Test public void test() {         
        RiakClient riakClient = mock(RiakClient.class);

        @SuppressWarnings("unchecked")
        RiakCommand<FetchValue.Response, Location> riakCommand = (RiakCommand<FetchValue.Response, Location>) mock(RiakCommand.class);

        Response response = mock(Response.class);
        when(riakClient.execute(riakCommand)).thenReturn(response);
        Response returnedResponse = riakClient.execute(riakCommand);

        when(response.getValue(Object.class)).thenReturn(new Object());
        MyPojo myData = returnedResponse.getValue(MyPojo.class);
        // Make assertions
    }

Как выполнить модульное тестирование кода, использующего клиент riak? В конце концов я хотел бы убедиться, что используется ожидаемая комбинация типа/блока/ключа и что ожидаемый RiakCommand запущен.

РЕДАКТИРОВАТЬ: я углубился в класс FetchValue и нашел эту структуру:
FetchValue - это public final

FetchValue.Response
 – это public static,
 – имеет пакетно-приватный конструктор Response(Init<?> builder).

FetchValue.Response.Init<T> is:
- protected static abstract class Init<T extends Init<T>> extends KvResponseBase.Init<T>

И есть FetchValue.Response.Builder:
static class Builder extends Init<Builder>
- с build(), который: return new Response(this);

Я предполагаю, что Mockito теряется где-то среди внутренних классов, и мой вызов заканчивается в KvResponseBase.convertValues, где выбрасывается NP. KvResponseBase.convertValues предполагает List<RiakObject> значений, и я не вижу разумного способа его присвоения.


person Mrtn    schedule 10.02.2015    source источник


Ответы (3)


Я немного расследовал ваш случай. Я сократил ваш пример до этого простого SSCCE:

import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import org.junit.Test;
import com.basho.riak.client.api.commands.kv.FetchValue.Response;

public class RiakTest {
    @Test
    public void test() throws Exception {
        Response response = mock(Response.class);
        given(response.getValue(Object.class)).willReturn(new Object());
    }
}

который выдает эту ошибку:

java.lang.NullPointerException
at com.basho.riak.client.api.commands.kv.KvResponseBase.convertValues(KvResponseBase.java:243)
at com.basho.riak.client.api.commands.kv.KvResponseBase.getValue(KvResponseBase.java:150)
at com.basho.riak.client.api.commands.kv.FetchValue$Response.getValue(FetchValue.java:171)
at RiakTest.test(RiakTest.java:12)

После некоторого копания, я думаю, что определил проблему. Дело в том, что вы пытаетесь заглушить общедоступный метод, унаследованный от класса пакета (видимости):

abstract class KvResponseBase {
    public <T> T getValue(Class<T> clazz) {
    }
}

Кажется, что Mockito не может заглушить этот метод, поэтому вызывается настоящий метод и выдается NullPointerException (из-за доступа нулевого члена: values). Важно отметить, что если вызов этой функции не завершится ошибкой, Mockito покажет правильную ошибку:

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.
       Mocking methods declared on non-public parent classes is not supported.
    2. inside when() you don't call method on mock but on some other object.

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


ОБНОВИТЬ

проблема, которую я открыл, на самом деле является дубликатом существующий. Эта проблема не будет исправлена, но существует обходной путь. Вы можете использовать макет Bytebuddy вместо cglib. Пояснения можно найти здесь.

person gontard    schedule 12.02.2015
comment
Принятый. Ваши находки вокруг Mockito действительно впечатляют. Вы помогли мне понять, что Mockito не идеальный инструмент для этой задачи. Теперь, когда вы ознакомились с кодом riakClient, есть ли у вас предложения по тестированию кода, который его использует? Спасибо! - person Mrtn; 12.02.2015
comment
@Mrtn рад помочь. Я думаю, вам следует задать новый вопрос, раскрывая очистку того, что вы пытаетесь проверить. Я думаю, вы получите более широкую помощь, поскольку она связана не с mockito, а с (модульным) тестированием в целом. И даже если я немного посмотрю некоторые классы в Риаке, я уже не специалист ;) - person gontard; 12.02.2015

Вы не можете издеваться над классами final и методами final и/или static с помощью mockito. Обратите внимание, что static вложенных классов — это нормально. Это связано с тем, что подклассы mockito (я не уверен на 100%, что это точная операция, она использует CGLIB для создания классов) объекты, но им не разрешено переопределять окончательные методы или расширять окончательные классы. Для методов static переопределение невозможно.

В своем коде вы, вероятно, пытаетесь вызвать окончательный класс или метод. Трудно сказать, какой класс вызывает проблему, из вашей NullPointer stackstrace вы должны подозревать первый объект на нем, над которым вы издевались (начиная с метода testcase). Метод на макете не должен вызывать какие-либо другие методы (ожидайте, что он будет внутренним для mockito), поэтому, вероятно, это окончательный вариант, потому что вы, похоже, не вызываете «издевательский» метод.

В вашем случае трассировка стека не завершена (поскольку на ней нет вашего теста). Бегло взглянув на структуру riak, я не смог найти способ взглянуть на FetchValue$Response.getValue.

Также обратите внимание на следующее. Из фрагмента, который вы опубликовали, я не могу сказать, что вы тестируете в своем тестовом примере. Все объекты, которые вы создаете, являются макетами. Обычно у вас есть 1 (или несколько) реальных классов, которые вы тестируете. Другие классы (которые взаимодействуют с вашими тестируемыми классами) вы имитируете, чтобы иметь возможность имитировать сложное поведение.

person Thirler    schedule 11.02.2015
comment
Я согласен с большей частью вашего ответа и особенно с последним абзацем: Какова цель этого теста? Но я должен сказать, что ваш диагноз неверен, я сделал базовый шаг в своем проекте, и классы/методы не являются окончательными, а методы не статичными. Я еще не нашел проблему. - person gontard; 12.02.2015
comment
Согласен, этот тест бессмысленный. Мой грандиозный план состоял в том, чтобы утверждать, что мое приложение передало ожидаемые аргументы RiakClient. Насмешки и сопоставление аргументов, казалось, были выходом. Теперь, благодаря @gontard, мы знаем больше, и мне нужно найти другое решение. Спасибо за внимание и время! - person Mrtn; 12.02.2015

Продолжение: спасибо @gontard, я смог найти это:

<dependency>
  <!-- We need this fix: https://github.com/mockito/mockito/pull/171 to use mockito with Riak -->
<!--http://stackoverflow.com/questions/28442495/how-to-mock-riak-java-client#28474106-->
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>2.0.52-beta</version>
  <scope>test</scope>
  </dependency>

В него включены исправления.

К сожалению, если вы используете как Fetch, так и MultiFetch (вероятно), вы в ручье.

MultiFetch.Response - это окончательный класс (поэтому вы можете использовать mockito, вам нужно использовать PowerMock). FetchValue.Response имеет проблемы, которые вы описали, и может быть исправлен только с помощью бета-мокито, пока недоступного с powermock...

Обновление, я понял, как использовать mockito и powermock вместе (до обновления powermock):

<!-- We need this to mock Multi-Fetch responses from Riak, which are final -->
<!-- However, we need the beta version of mockito due to bugs (see below),
so we _cannot_ use the mockito api provided by powermock, do _not_ include _powermock-api-mockito, it'll mess stuff up -->
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-module-junit4</artifactId>
  <version>1.6.4</version>
  <scope>test</scope>
</dependency>
<!--If we don't include this, we get: -->
<!--java.lang.IllegalStateException:
Extension API internal error: org.powermock.api.extension.proxyframework.ProxyFrameworkImpl could not be located in classpath.-->
<!-- it looks like this is due to some discrepancy in packaging with mockito 2, this may be fixed in Fall 2016:
https://groups.google.com/forum/#!topic/powermock/cE4T40Xa_wc -->
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-api-easymock</artifactId>
  <version>1.6.4</version>
</dependency>


<!-- We need this fix: https://github.com/mockito/mockito/pull/171 to use mockito with Riak -->
<!--http://stackoverflow.com/questions/28442495/how-to-mock-riak-java-client#28474106-->
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-core</artifactId>
  <version>2.0.52-beta</version>
  <scope>test</scope>
  </dependency>
person user2077221    schedule 30.04.2016