Mockito когда / то не возвращает ожидаемое значение

Я пытаюсь заглушить этот метод getKeyFromStream, используя "любые" совпадения. Я пытался быть более явным и менее явным (anyObject()), но кажется, что независимо от того, что я пытаюсь, эта заглушка не вернет fooKey в моем модульном тесте.

Мне интересно, потому что это защищено или есть что-то еще, что я пропускаю или делаю неправильно. У меня есть другие утверждения "когда / потом" в тестах, которые работают, но по какой-то причине здесь это не так.

Примечание: getKeyFromStream обычно использует byteArrayInputStream, но я пытаюсь сопоставить его с InputStream, я пробовал оба безрезультатно.

public class FooKeyRetriever() //Mocked this guy
{
    public FooKey getKey(String keyName) throws KeyException {

        return getKeyFromStream(getKeyStream(keyName, false), keyName);
    }

    //Stubbed this method to return a key object which has been mocked
    protected FooKey getKeyFromStream(InputStream keyStream, String keyName){
        //Some code
        return fooKey;
    }
}

Модульный тест

@Mock
private FooKeyRetriever mockKeyRetriever;

@Mock
private FooKey fooKey;

@Before
public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
}

@Test
public void testGetFooKey() throws Exception {



    when(foo.getKeyFromStream(any(InputStream.class),any(String.class))).thenReturn(fooKey);

    FooKey fooKey = mockKeyRetriever.getKey("irrelevant_key");

    assertNotNull(fooKey);
}

1 ответ

Решение

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

На самом деле есть два способа создания тестовых объектов: mock а также spy, Начинающий создаст новый объект на основе предоставленного вами класса с внутренним состоянием null, а также вернет null на каждый вызванный метод. Вот почему вам нужно определить определенные возвращаемые значения для вызовов методов. spy с другой стороны, создает реальный объект и перехватывает вызовы методов, если для определенных методов определены "ложные определения".

Mockito и PowerMock предоставляют два способа определения ваших смоделированных методов:

// method 1
when(mockedObject.methodToMock(any(Param1.class), any(Param2.class),...)
    .thenReturn(answer);
when(mockedObject, method(Dependency.class, "methodToMock", Parameter1.class, Parameter2.class, ...)
    .thenReturn(answer);

или же

// method 2
doReturn(answer).when(mockedObject).methodToMock(param1, param2);

Разница в том, что method 1 выполнит реализацию методов, в то время как последний не выполнит. Это важно, если вы имеете дело с spy объекты, так как вы иногда не хотите выполнять реальный код внутри вызванного метода, но вместо этого просто замените код или верните предопределенное значение!

Хотя Mockito и PowerMock обеспечивают doCallRealMethod() который вы можете определить вместо doReturn(...) или же doThrow(...)это вызовет и выполнит код в вашем реальном объекте и проигнорирует любые операторы возврата смоделированного метода. Тем не менее, это не так полезно в вашем случае, когда вы хотите смоделировать метод тестируемого класса.

Реализация метода может быть "перезаписана"

doAnswer(Answer<T>() { 
    @Override 
    public T answer(InvocationOnMock invocation) throws Throwable {
        ...
    }
)

где вы можете просто объявить, какой должна быть логика вызванного метода. Вы можете использовать это, чтобы возвратить фиктивный результат защищенного метода, поэтому вот так:

import static org.hamcrest.core.IsSame.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;

import java.io.InputStream;

import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class FooKeyRetrieverTest {

    @Test
    public void testGetFooKey() throws Exception {
        // Arrange
        final FooKeyRetriever sut = spy(new FooKeyRetriever());
        FooKey mockedKey = mock(FooKey.class);

        doReturn(mockedKey)
            .when(sut).getKeyFromStream(any(InputStream.class), anyString());
        doAnswer(new Answer<FooKey>() {

            public FooKey answer(InvocationOnMock invocation) throws Throwable {
                return sut.getKeyFromStream(null, "");
            }
        }).when(sut).getKey(anyString());

        // Act
        FooKey ret = sut.getKey("test");

        // Assert
        assertThat(ret, sameInstance(mockedKey));
    }
}

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

doReturn(mockedKey).when(sut).getKey(anyString());

Попытка только изменить getKeyFromStream(...) с чем-то вроде этого:

doReturn(mockedKey)
    .when(sut).getKeyFromStream(any(InputStream.class), anyString());

без изменения getKey(...) вашей тестируемой системы (SUT) ничего не добьется как настоящий код getKey(...) будет казнен. Однако, если вы издеваетесь над объектом sut, вы не можете вызвать метод в вашем // Act раздел, как это будет возвращать ноль. Если вы попытаетесь

doCallRealMethod().when(sut).getKey(anyString());

на фиктивном объекте вызывается реальный метод woulb, и, как упоминалось ранее, это также будет вызывать реальные реализации getKeyFromStream(...) а также getKeyStream(...) независимо от того, что вы указали как mock-метод.

Как вы, вероятно, можете видеть сами, методы насмешки вашего тестируемого класса не так уж полезны и налагают на вас больше бремени, чем приносят какую-либо пользу. Поэтому вы или ваша компания должны придерживаться политики, если вы хотите или должны тестировать закрытые / защищенные методы вообще, или если вы придерживаетесь тестирования только общедоступного API (который я рекомендую). У вас также есть возможность провести рефакторинг вашего кода, чтобы улучшить тестируемость, хотя основной целью рефакторинга должно быть улучшение общего дизайна вашего кода.

Другие вопросы по тегам