2

I have the following structure

public class A{...}

public class B extends A{
    private C cObject;
    private Object otherValue;

    B(){
       cObject = new C(this);
    }
}

public class C{
    private B bObject;

    C(B bObject){
       this.bObject = bObject;
    }
}

I want to test a method of the class B. I am using mockito because I need to mock another method of B in order to test the method I want to test. So In my test I declare the B object as @Spy, then I call MockitoAnnotations.initMocks(this); and finally I mock the method using doReturn().when().method();

Now I run the test and it fails. My surprise comes when I debug it and I realize that when I am in the object B, the value of the field otherValue is for example X but when I am in C, the value of this.bObject.otherValue is not X but null.

Since I've created the object C within B with new C(this);, shouldn't both B and C.bObject be the same object and contain therefore the same values?

NOTE: I've also tried it without spying the object and mocking the method and it works, so the real question is:

Does Mockito replace my object with another object when I spy it? And in that case, what can I do to make it work?

EDIT an easier explanation could be: I want to test an object B, this object B creates an instance of an object C passing itself (this) as parameter. I need to Spy the object B so I create in my test an instance of B and then call MockitoAnnotations.initMocks(this);. Are both objects (the one in the test and the one in the instance of C) after this calling still the same object or mockito replaced it with a new one?

iberbeu
  • 15,295
  • 5
  • 27
  • 48

1 Answers1

2

The B instance will be constructed before being spied, so the reference that C receives is the actual B object and not the spied instance.

You're adding behaviour to the spied instance which is indeed a different object to the one that C.bObject has, so the behaviour is not applied. Similarly setting b.otherValue will not result on b.cObject.bObject.otherValue being set.

You can see that these are different objects - assuming a default toString is present:

final B b = Mockito.spy(new B());

System.out.println("spied: " + b);
System.out.println("b.cObject.bObject: " + b.cObject.bObject);

It should produce something along the lines of:

spied: B$$EnhancerByMockitoWithCGLIB$$999ce15d@7a187814
b.cObject.bObject: B@5c73a7ab

Perhaps you could use reflection to set b.cObject.bObject field to the spied instance? For example:

final Field f = C.class.getDeclaredField("bObject");
f.setAccessible(true);
f.set(b.cObject, b);
Jonathan
  • 20,053
  • 6
  • 63
  • 70
  • ´spied: B$$EnhancerByMockitoWithCGLIB$$999ce15d@7a187814´ that's the point. I saw it in the watch section of Eclipse. Is there any work around for this situation? – iberbeu May 16 '13 at 10:06
  • Unfortunately I'm not really sure what to suggest - I would normally say pass in the spied object but it's not that easy in this case. Is there no way of breaking the circular reference? – Jonathan May 16 '13 at 10:13
  • there's actually no way to break it because it is legacy code, so I just need a way to handle it. And I cannot pass the mocked object after creating it because there is no 'set' method for it... – iberbeu May 16 '13 at 14:53
  • @iberbeu I thought that might be so. Have you thought about using reflection to inject the spied instance? See my updated answer - not sure if that would be possible for your code though. – Jonathan May 16 '13 at 15:25
  • reflection was my last option but I think it is what I need... thanks for your answer, i'll try it – iberbeu May 16 '13 at 16:00
  • Ok, that doesn't seem to be possible as well. For injecting the spied instance I have to do reflection on the spied instance itself and since it has been spied it is not a normal object anymore and the fields don't have the same names. Another idea? – iberbeu May 16 '13 at 17:20