Mockito and Circular Dependency: How to Invoke Actual Class Implementations

68 views Asked by At

The following classes reference each other crating a circular dependency:

@AllArgsConstructor
@Component
public class A {

    private B b;

    public String doSomething() {
        return "Result: " + b.doSomethingElse();
    }
}

@Componenet
public class B {

    private A a;

    @Autowired
    public B(@Lazy final A a) {
        this.a = a;
    }

    public String doSomethingElse() {
        return a.toString();
    }
}

I need to test class A and make sure the actual implementation of class B gets invoked:

@RunWith(MockitoJUnitRunner.class)
public class ATest {

    @InjectMocks
    private A a;

    @Spy
    private B b;

    @Test
    public void testDoSomething() {
        final String result = a.doSomething();
        assertNotNull(result);
    }
}

@Spy doesn't work and I always get the following error:

Unable to initialize @Spy annotated field 'b'.
Please ensure that the type 'B' has a no-arg constructor.
org.mockito.exceptions.base.MockitoException: Unable to initialize @Spy annotated field 'b'.
Please ensure that the type 'B' has a no-arg constructor.

I could use @Mock... but then I need to mock the expected result, whereas I need to invoke the actual implementation (that's why I'm using @Spy).

Is there any way to workaround this? Thank you very much :-)

1

There are 1 answers

0
knittl On

Do what Spring would do for a @Lazy dependency: don't inject the dependency directly, but a proxy instance which allows setting the dependency lazily. You can avoid some problems by depending on interfaces and not concrete classes.

class LazyA extends A {
  private A realA;

  public LazyA() { this(null); }
 
  public void setRealA(final A realA) {
    this.realA = realA;
  }

  public String doSomething() {
    return realA.doSomething();
  } 
}

public class ATest {
  @Test
  public void testDoSomething() {
    final LazyA lazyA = new LazyA();
    final B b = new B(lazyA);
    final A a = new A(b);
    lazyA.setRealA(a);

    final String result = a.doSomething();
    assertNotNull(result);
  }
}

Better yet: don't write code with circular dependencies.