I have a bean like this:
@ApplicationScoped
public class MyService {
@Inject
@All
List<MyDataProvider> dataProviders;
public List<String> getAllData() {
var data = new ArrayList<String>();
for (var provider : dataProviders) {
data.add(provider.getData());
}
return data;
}
}
And here is the MyDataProvider
interface:
public interface MyDataProvider {
String getData();
}
Let's say, there are tens of beans, implementing the MyDataProvider
interface.
I'd like to replace them all with a specific set of mocks within a unit test and I struggle to find a proper way to do this.
I can mock particular implementations of MyDataProvider
using the @InjectMock
or QuarkusMock.installMockForInstance()
, I just don't know how to prevent injection of the rest non-mocked beans during the test (I need only a couple of mocks to test basic functionality).
So, is there a way to completely override the set of beans being injected in such a field during testing?
The solution I ended up with looks like this.
I replaced @All List<MyDataProvider>
with Instance<MyDataProvider>
and mocked its iterator()
method:
@ApplicationScoped
public class MyService {
@Inject
Instance<MyDataProvider> dataProviders;
public List<String> getAllData() {
var data = new ArrayList<String>();
for (var provider : dataProviders) {
data.add(provider.getData());
}
return data;
}
}
@QuarkusTest
class MyServiceTest {
@Mock
Instance<MyDataProvider> dataProviders;
@InjectMocks
MyService myService;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void doTest() {
List<MyDataProvider> mocks = ... // initialize a list of MyDataProvider mocks
Mockito.when(dataProviders.iterator()).thenReturn(mocks.iterator());
// now myService.dataProviders will act as if it only contains mocked beans
}
}
I don't find it particularly nice, I think there should be a more straightforward way to do this, but at least it works.
Turns out, injecting Mocks with Mockito doesn't play well with CDI features of Quarkus. For example, bean validation doesn't work for class instances annotated with @InjectMocks
.
I decided to use an intermediate @ApplicationScoped
bean, which provides a list of classes implementing MyDataProvider
interface:
@ApplicationScoped
public class MyDataProviderRegistry implements Iterable<MyDataProvider> {
@Inject
Instance<MyDataProvider> dataProviders;
@Override
public Iterator<MyDataProvider> iterator() {
return dataProviders.iterator();
}
}
@ApplicationScoped
public class MyService {
@Inject
MyDataProviderRegistry dataProviders;
...
}
This way I can mock the MyDataProviderRegistry
using QuarkusMock tooling, keeping ability to use all CDI features the usual way.