Mockito Spy Does Not See Correct Primitive Value

Tags: , , ,



I am facing the below problem where, when indirectly updating the fields on a spy object, the spy does NOT see updates on primitive fields, whereas it sees on reference once.

As an example:

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);
    }
}

The last verification, will fail, as the method is called with the first primitive value.

I was wondering why this is happenning? Is this an expected behaviour. Running the above test, will produce the below logging:

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

I would expect the last log statement to be invoked with false.

Any insights on the above?

PS: Version=mockito-core:2.25.0

Answer

There are 2 instances of TestClass object in your system:

  • original instance
  • spy instance

The spy is created from original instance.

  • primitive fields (like boolean field) are copied, each TestClass holds
  • references are copied, but underlying, referenced object is shared (refField)

Lets make following changes to observe that:

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);
}

Which gives the following output:

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

You can clearly see:

  • two instances of TestClass
  • actor sees the original object, not the spy
  • when actor makes the change to the primitive field, it changes the original
  • primitive field in the spy remains unchanged

Possible solutions

You need to make the actor see the spy, not the original object. To achieve that, creation of TestClass must be decoupled from registering the consumer.

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);
    // ...
}


Source: stackoverflow