Reputation: 1358
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
Reputation: 51343
Mockito uses different strategies when injecting mocks in this order:
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