5

I have a Wrapper class that causes that instead of equals method, the method equalsWithoutId is called on wrapped object. Implementation here:

import org.apache.commons.lang3.Validate;

public class IdAgnosticWrapper {

    private final IdAgnostic wrapped;

    public IdAgnosticWrapper(IdAgnostic wrapped) {
        Validate.notNull(wrapped, "wrapped must not be null");
        this.wrapped = wrapped;
    }

    @Override
    public int hashCode() {
        return wrapped.hashCodeWithoutId();
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof IdAgnosticWrapper)) {
            return false;
        }
        return wrapped.equalsWithoutId(((IdAgnosticWrapper) obj).getWrapped());
    }

    public IdAgnostic getWrapped() {
        return wrapped;
    }
}

IdAgnostic is a simple interface, that ensures that required methods are present

public interface IdAgnostic {
    int hashCodeWithoutId();
    boolean equalsWithoutId(Object o);
}

Then I have some Unit tests that should test, if equals() delegates to wrapped#equalsWithoutId() method and hashCode() delegates to wrapped#hashCodeWithoutId.

Now I try to test it by these tests

import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import static org.mockito.Mockito.verify;

public class IdAgnosticWrapperTest {

    @Mock
    private IdAgnostic wrappedMock;

    @InjectMocks
    private IdAgnosticWrapper tested;


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

    @Test
    public void testEquals_EqualsWithoutIdIsCalledOnWrapped() throws Exception {
        tested.equals(tested);
        verify(wrappedMock).equalsWithoutId(tested.getWrapped());
    }

    @Test
    public void testHashCode_HashCodeWithoutIdIsCalledOnWrapped() throws Exception {
        tested.hashCode();
        verify(wrappedMock).hashCodeWithoutId(); //line 34
    }
}

As you can see, I just create wrapped mock and I test, whether equals and hashcode delegate the functionality.

If I run tests separately, everything works fine, but if I run both tests sequentially, the second one fails with this message

Wanted but not invoked:
wrappedMock.hashCodeWithoutId();
-> at com.my.app.utils.IdAgnosticWrapperTest.testHashCode_HashCodeWithoutIdIsCalledOnWrapped(IdAgnosticWrapperTest.java:34)
Actually, there were zero interactions with this mock.

Why does this happen?

Michal Krasny
  • 5,434
  • 7
  • 36
  • 64
  • You might try executing it in the debugger. It might be clearer then. – David M. Karr Mar 05 '16 at 16:22
  • I did, but I found nothing interesting. – Michal Krasny Mar 05 '16 at 16:42
  • The point was, to set a breakpoint in the "hashcode" method and see if it got to that line of code. If it does, inspect the "wrapper" object and make sure it's a mock object. I see no reason it would not be, but those are important things to check. – David M. Karr Mar 05 '16 at 16:46
  • If that doesn't give you a clue, and you don't get a response here, write a note on the mockito-user mailing list, with all of your details. – David M. Karr Mar 05 '16 at 16:47
  • If you're using apache commons, if you upgrade you can get access to the "Diffable" interface - it may be what you want to consider implementing. Based on what you have here it looks like you want to make some sort of "business equality" logic *not* based on an ID of a key in the database, unless I'm totally off-base here. – MetroidFan2002 Mar 05 '16 at 16:49
  • @DavidM.Karr I did that before I asked this question. The code goes through the hashcode function as seems to do what it is expected to do. – Michal Krasny Mar 05 '16 at 16:53
  • @MetroidFan2002 thanks for the tip, I will try it. However I'm curious what's wrong with the tests. – Michal Krasny Mar 05 '16 at 16:54
  • Dang, I should have noticed the final. Note that if you have a situation where you can't change that, this is one thing that PowerMock(ito) can do (deal with finals), but in general you should use Mockito when you can, PowerMock when you have to. – David M. Karr Mar 05 '16 at 22:02
  • Thank you for the tips, please see the communication in the provided answer. I think, that problem was elsewhere. – Michal Krasny Mar 10 '16 at 14:12

1 Answers1

3

Effectively, what's happening here is that the @InjectMocks isn't able to correctly inject the constructor parameter wrapped. According to the Javadoc for @InjectMocks, this is the current behavior. So unless you want to use setter injection, you will need to remove the @InjectMocks annotation. See the revised code:

public class IdAgnosticWrapperTest {

  @Mock
  private IdAgnostic wrappedMock;

  private IdAgnosticWrapper tested;

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

    this.tested = new IdAgnosticWrapper(this.wrappedMock);
  }

  @Test
  public void testEquals_EqualsWithoutIdIsCalledOnWrapped()
      throws Exception {
    tested.equals(tested);
    verify(wrappedMock).equalsWithoutId(tested.getWrapped());
  }

  @Test
  public void testHashCode_HashCodeWithoutIdIsCalledOnWrapped()
      throws Exception {
    tested.hashCode();
    verify(wrappedMock).hashCodeWithoutId(); //line 34
  }
}

When I ran the above code, both tests ran together sequentially and passed.

entpnerd
  • 10,049
  • 8
  • 47
  • 68
  • 2
    `@InjectMocks` does *not* mock objects in fields it annotates, but instead allows Mockito to reflectively install/inject mocks in `@Mock` and `@Spy` fields into constructors and fields of the `@InjectMock`ed object. That said, you're right about `@InjectMocks` being a problem, as TestNG reuses test objects and Mockito fails to field inject IdAgnosticWrapper due to the field''s final modifier. See more [here](http://stackoverflow.com/questions/20046210/mockito-injectmocks-strange-behaviour-with-final-fields). – Jeff Bowman Mar 07 '16 at 16:50
  • @JeffBowman good call. I totally missed that. I have updated my answer accordingly. Thanks for making me do my homework. :-) – entpnerd Mar 09 '16 at 14:44
  • 2
    Thank you for the provided solution, however the problem was not in the fact, that wrapped field is final. Problem was, that MockitoAnnotations.initMocks(this); created new instance of private IdAgnostic wrappedMock;, but did not create a new instance of private IdAgnosticWrapper tested; Therefore I verified that method was called on the object, that was never wrapped by wrapper (there was the instance from the previous test). This question seems to be a duplicate of http://stackoverflow.com/questions/20046210/mockito-injectmocks-strange-behaviour-with-final-fields – Michal Krasny Mar 10 '16 at 14:11