Filosssof
Filosssof

Reputation: 1358

How does Mockito handling Suppliers?

enter image description here

enter image description here

On the first screenshot you can see my test class. This class is annotated with @ExtendWith({MockitoExtension.class}) and also the tested service is annotated with @InjectMocks. On the second screenshot you can see the tested service.

Why does Mockito uses the long supplier in both cases?

Upvotes: 0

Views: 2419

Answers (1)

René Link
René Link

Reputation: 51343

Mockito uses different strategies when injecting mocks in this order:

  1. Constructor injection
  2. Property setter injection
  3. Field injection

Field injection will not work in your example, since the service's fields are declared final.

Since the fields are declared final and the code snippet you showed does not have a field initializer, I assume that you have a constructor with the Supplier args. E.g.

public SomeService(Supplier<String> stringSupplier, Supplier<Long> longTimeSupplier) {
    this.stringSupplier = stringSupplier;
    this.longTimeSupplier = longTimeSupplier;
}

Thus Mockito will try the constructor injection, find the constructor with the two Supplier parameters and tries to resolve the arguments.

Mockito then finds the two Supplier mocks in the test, but it can not see the generic type due to type erasure. Thus Mockito sees the constructor like this:

public SomeService(Supplier stringSupplier, Supplier longTimeSupplier)

Mockito can also not decide which Supplier to use based on the parameter name, because the normal Java reflection API does not provide that information. So the name of the mocks, will not be taken into account.

There are libraries like paranamer that read the bytecode and extract the debug information to read the parameter names, but Mockito doesn't use that libs.

Thus Mockito just injects the first matching mock which is Supplier<String> stringSupplier in your case. Even your issues is related to generics, Mockito would also act the same way when you have two parameters of the same type that are not generic.

I assumed that you have a constructor that takes the two Supplier. So you can just invoke it in your test's before.

@BeforeEach
public void setup() {
    service = new SomeService(stringSupplier, longSupplier);
}

If you can not access the constructor, e.g. it has package scope, you need to invoke it using reflection and set the accessible property to true

@BeforeEach
public void setup() throws Exception {
    Constructor<SomeService> constructor = SomeService.class.getConstructor(Supplier.class, Supplier.class);
    constructor.setAccessible(true);
    service = constructor.newInstance(stringSupplier, longSupplier);
}

PS If you want to remove the final, make sure that the mocks are either named after the fields in the service longTimeSupplier vs. longSupplier or you use @Mock(name = "longTimeSupplier").

Upvotes: 2

Related Questions