0

I want to mock object A a return from B.foo().

I've tried mock A with @Mock, it didn't work.

class SomeClass() {
    public void doSomeThing() {
        B b = new B();
        A a = b.foo();
        a.foo();
    }
}
@Mock
A a;

@InjectMock
SomeClass someClass;

@Test
void test() {
    Mockito.when( a.foo() ).thenReturn( something );
    
    assertDoesNotThrow( () -> someClass.doSomeThing() );
}

How can I mock A?

Rurien
  • 359
  • 1
  • 13
  • 1
    In your current structure, you can't. If you want to be able to inject a mock you can't be `new`-ing up `B` inside the method. – jonrsharpe Dec 02 '22 at 09:21
  • Does this answer your question? [Why is my class not calling my mocked methods in unit test?](https://stackoverflow.com/questions/74027324/why-is-my-class-not-calling-my-mocked-methods-in-unit-test) – knittl Dec 03 '22 at 13:36

3 Answers3

2
class SomeClass {
    public void doSomeThing() {
        B b = new B();
        A a = b.foo();
        a.foo();
    }
}

Since your code creates a new instance of class B, it is not possible to mock the behavior of said object. SomeClass and B are tightly coupled and SomeClass depends on a concrete implementation of B.

You can refactor your class to become testable. To do that, allow consumers of your class to inject the behavior how B is created. One possible solution:

class SomeClass {
  private final Supplier<? extends B> bFactory;
  public SomeClass(final Supplier<? extends B> bFactory) {
    this.bFactory = bFactory;
  }

  // Production code can use the parameterless constructor to get the old behavior
  // But this is mostly to help with migration, real code should use the parameterized constructor too
  public SomeClass() {
    this(B::new);
  }

  public void doSomeThing() {
    B b = this.bFactory.get();
    A a = b.foo();
    a.foo();
  }
}

Now you can inject a supplier which supplies a mocked instance of your class in your tests:

@Test
void test() {
  final A aMock = mock(A.class);
  when(aMock.foo()).thenAnswer(a -> /* ... */);
  final B bMock = mock(B.class);
  when(bMock.foo()).thenReturn(aMock);
  final SomeClass someClass = new SomeClass(() -> bMock);
  assertDoesNotThrow( () -> someClass.doSomeThing() );
}

Note that it is generally considered bad practice to have mocks returning mocks. Such a setup makes your tests brittle, unnecessarily complex, and coupled to the implementation.

Find an exhaustive problem statement and alternative solutions in the post Why is mocking static method with Mockito not working in my case?

knittl
  • 246,190
  • 53
  • 318
  • 364
  • I totally agree with your answer except "Note that it is generally considered bad practice to have mocks returning mocks." My first impulse here is to disagree because I can not see any means to avoid it for the code in the question. – Sascha Doerdelmann Dec 03 '22 at 20:35
-2

Try with this @Mock B b;

Mockito.when(b.foo()).thenReturn(somePreparedObject);

z48o0
  • 98
  • 7
  • This will not work. A `new B` instance is created inside the class. This instance is not mocked. See [Why is my class not calling my mocked methods in unit test?](https://stackoverflow.com/q/74027324/112968) – knittl Dec 03 '22 at 13:38
-2

MockitoAnnotations.openMocks(this) method has to be called to initialize annotated objects.

See Annotation Type Mock.

So the test class should look like the following:

public class SomeClassTest {
    @Mock
    A a;

    @InjectMocks
    SomeClass someClass;

    private AutoCloseable closeable;

    @BeforeEach
    public void openMocks() {
        closeable = MockitoAnnotations.openMocks(this);
    }

    @AfterEach
    public void releaseMocks() throws Exception {
        closeable.close();
    }

    @Test
    void test() {
        Mockito.when( a.foo() ).thenReturn(true);
        Assertions.assertDoesNotThrow( () -> someClass.doSomeThing() );
    }
}

Edit

After receiving two downvotes on the answer, I took a second look at the question and recognized, that my code did solve an Exception issue but still did not anwer the question.

If we add the following annotation to the test class, the test will indeed fail with an Exception:

@MockitoSettings(strictness = Strictness.STRICT_STUBS)

The error message is: "Unnecessary stubbings detected."

So my code did create a Mock a as member of SomeClassTest but it did not replace the local variable a in SomeClass.doSomeThing. The anotation InjectMockshas no means to replace that local variable by a Mock. As stated in the documentation:

Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection

So the only way to mock local Variable a in SomeClass.doSomeThing is by also mocking local Variable b and returning the member a of SomeClassTest. And as b can not be injected along the same arguments as for a, you need to refactor your code in a way, that a and b can get mocked. (As knittl already provided this solution, i will not repeat it.)