Mockito: проверка макета (с помощью "RETURNS_DEEP_STUBS" ) Возвращает больше вызовов, чем ожидалось

Глядя на код ниже, я ожидаю, что вызов getSand() произойдет один раз, но тест не работает с четырьмя вызовами. Где происходят эти звонки? Я хочу написать тест, чтобы гарантировать, что для getSand() выполняется только один вызов.

Источник

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class DeepSandTest {

    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    SandBox mockSandBox;

    @Test
    public void should(){
        when(mockSandBox.getSand().doA()).thenReturn(1);
        when(mockSandBox.getSand().doB()).thenReturn(1);
        when(mockSandBox.getSand().doC()).thenReturn(1);

        DeepSand deepSand = new DeepSand(mockSandBox);
        deepSand.getTipple();

        verify(mockSandBox, times(1)).getSand();
    }

    public class DeepSand{

        private SandBox sandBox;

        public DeepSand(SandBox sandBox) {
            this.sandBox = sandBox;
        }

        public void getTipple(){
            Sand sand = sandBox.getSand();
            sand.doA();
            sand.doB();
            sand.doC();
        }
    }

    public interface SandBox{
        public Sand getSand();
    }

    public interface Sand{
        public Integer doA();
        public Integer doB();
        public Integer doC();
    }
}

Выход

org.mockito.exceptions.verification.TooManyActualInvocations: 
mockSandBox.getSand();
Wanted 1 time:
-> at DeepSandTest.should(DeepSandTest.java:26)
But was 4 times. Undesired invocation:
-> at DeepSandTest.should(DeepSandTest.java:20)

Подробнее Java 1.6, JUnit 4.11, Mockito 1.9.5

Извлеченные уроки

Если вы думаете о глубоких заглушках как о древе макетных объектов, тогда вы должны только проверять листья ( "последний макет в цепочке" ), потому что узлы включены в цепочку вызовов, необходимую для настройки поведения листьев. Чтобы выразить это другим способом, узлы вызываются во время установки листьев.

Ответ 1

Он подсчитывает вашу настройку как invocations, так как глубокие заглушки не поддерживается в API проверки и жалуется на второй вызов, который:

when(mockSandBox.getSand().doB()).thenReturn(1);

Я бы пропустил использование RETURNS_DEEP_STUBS и просто использовал другой макет:

...
@Mock
SandBox mockSandBox;

@Mock
Sand sand;

@Test
public void should(){
    when(mockSandBox.getSand()).thenReturn(sand);
    when(sand.doA()).thenReturn(1);
    when(sand.doB()).thenReturn(1);
    when(sand.doC()).thenReturn(1);
...

Ответ 2

Из документации Answers.RETURNS_DEEP_STUBS:

Please see the {@link org.mockito.Mockito#RETURNS_DEEP_STUBS} documentation for more details.

От Mockito.RETURNS_DEEP_STUBS:

Verification only works with the last mock in the chain. You can use verification modes. 
[...]
when(person.getAddress(anyString()).getStreet().getName()).thenReturn("deep");
[...]
inOrder.verify(person.getAddress("the docks").getStreet(), times(1)).getName();

Итак, я думаю, чтобы заставить ваши проверки работать, вы должны переписать свои Mocks на что-то вроде этого:

@Mock
SandBox mockSandBox;

@Mock
Sand mockSand;

@Test
public void should()
{
    when( mockSand.doA() ).thenReturn( 1 );
    when( mockSand.doB() ).thenReturn( 1 );
    when( mockSand.doC() ).thenReturn( 1 );

    when( mockSandBox.getSand() ).thenReturn( mockSand );

    DeepSand deepSand = new DeepSand( mockSandBox );
    deepSand.getTipple();

    verify( mockSandBox, times( 1 ) ).getSand();
}

Или проверяйте только вызовы doA, doB и doC и не проверяйте вызов getSand(). - Это зависит от того, что именно вы хотите проверить здесь.