javaunit-testingmockitoquarkuscdi

Inject a list of mocks during testing in Quarkus


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?


Solution

  • 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.

    Update

    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.