Mockito Spy не видит правильного примитивного значения - PullRequest
2 голосов
/ 09 июля 2020

Я сталкиваюсь с проблемой ниже, когда при косвенном обновлении полей на шпионском объекте шпион НЕ видит обновлений в примитивных полях, тогда как он видит ссылку один раз.

В качестве примера:

import org.junit.Test;
import org.mockito.Mockito;

import java.util.function.Consumer;
import java.util.concurrent.atomic.AtomicBoolean;

public class MyTest {

    class Actor {
        Consumer<Boolean> consumer;

        void init(Consumer<Boolean> consumer){
            this.consumer = consumer;
        }

        void act(boolean flag){
            this.consumer.apply(flag);
        }
    }

    class TestClass {
        boolean field = true;
        AtomicBoolean refField = new AtomicBoolean(true);

        TestClass(Actor actor){
           actor.init( flag -> {
               System.out.println("Changing field to " + flag);
               field = flag;
               refField.set(flag);
           });
        }

        void call(){
            this.doSomething(field);
        }
   
        void callRef(){
            this.doSomething(refField.get());
        }

        void doSomething(boolean flag){
            System.out.println("#doSomething(" + flag + ")");
        }
    }

    @Test
    public void test(){
        // given an actor and a spied TestClass
        Actor actor = new Actor();
        TestClass spy = Mockito.spy(new TestClass(actor));

        // when invoking the action with the primitive
        spy.call();
        // then expect correct invocation
        Mockito.verify(spy, Mockito.times(1)).doSomething( true );

       // when invoking the action with the ref field
       spy.callRef();
       // then expect correct invocation
       Mockito.verify(spy, Mockito.times(2)).doSomething( true );

       // when changing the flag to 'false'
       actor.act( false );

       // and invoking the action with the refField
       spy.callRef();
       // then expect correct invocation
       Mockito.verify(spy, Mockito.times(1)).doSomething(false);

      // when invoking the action with the primitive
      spy.call();
      // then method is NOT invoked as expected !!!!!!!
      Mockito.verify(spy, Mockito.times(2)).doSomething(false);
    }
}

Последняя проверка не удастся, так как метод вызывается с первым примитивным значением.

Мне было интересно, почему это происходит ? Это ожидаемое поведение. Выполнение вышеуказанного теста приведет к следующему журналу:

#doSomething(true)
#doSomething(true)
Changing flag to false
#doSomething(false)
#doSomething(true)

Я ожидаю, что последний оператор журнала будет вызываться с помощью false.

Есть какие-либо идеи по вышеизложенному?

PS: Версия = mockito-core:2.25.0

1 Ответ

1 голос
/ 09 июля 2020

В вашей системе есть 2 экземпляра объекта TestClass:

  • исходный экземпляр
  • экземпляр шпиона

Шпион создается из исходного экземпляра.

  • примитивные поля (например, boolean field) копируются, каждый TestClass содержит
  • ссылки копируются, но базовый объект, на который имеется ссылка, является общим (refField)

Давайте внесем следующие изменения, чтобы увидеть, что:

TestClass(Actor actor){
    actor.init( flag -> {
        // print referenced TestClass instance 
        System.out.println("from actor: " + TestClass.this);
        System.out.println("Changing field to " + flag);
        field = flag;
        refField.set(flag);
    });
}

@Test
public void test(){
    // given an actor and a spied TestClass
    Actor actor = new Actor();
    TestClass testClassOriginal = new TestClass(actor);
    TestClass spy = Mockito.spy(testClassOriginal);
    System.out.println("org" + testClassOriginal);
    System.out.println("spy" + spy);
  
    // when changing the flag to 'false'
    actor.act( false );
    System.out.println("After change org: " + testClassOriginal.field);
    System.out.println("After change spy: " + spy.field);
}

Что дает следующий результат:

org: mypackage.MyTest$TestClass@4218500f
spy: mypackage.MyTest$TestClass$MockitoMock$2115616475@5c10f1c3
from actor: mypackage.MyTest$TestClass@4218500f
Changing field to false
After change org: false
After change spy: true

Вы можете ясно видеть:

  • два экземпляра TestClass
  • актер видит исходный объект, а не шпион
  • когда актер вносит изменения в примитивное поле, он меняет исходное
  • примитивное поле в шпионском поле остается без изменений

Возможные решения

Вам нужно, чтобы актер видел шпиона, а не исходный объект. Для этого создание TestClass должно быть отделено от регистрации потребителя.

class TestClass {
    boolean field = true;
    AtomicBoolean refField = new AtomicBoolean(true);

    TestClass(){
    }

    void consumeFlag(Boolean flag) {
        System.out.println("from actor: " + this);
        System.out.println("Changing field to " + flag);
        field = flag;
        refField.set(flag);
    }
    // ...
}

@Test
public void test(){
    // given an actor and a spied TestClass
    Actor actor = new Actor();
    TestClass spy = Mockito.spy(new TestClass());
    actor.init(spy::consumeFlag);
    // ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...