java - Inject a list of mocks during testing in Quarkus - Stack Overflow

admin2025-04-28  2

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?

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?

Share Improve this question asked Jan 9 at 18:18 UltraniumUltranium 3724 silver badges25 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 0

I can't think of a good solution. In theory, you can make all the mocks that should be injected in MyService#dataProviders CDI alternatives (simply annotate the mock bean class with @Alternative and @Priority(x)) with the same priority (higher than any other possible priority). This way only the mocks should be injected - given the fact that both @Inject Instance<> and @All List<> inject disambiguated beans. See also Unsatisfied and ambiguous dependencies and The Instance interface in the spec.

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.

转载请注明原文地址:http://anycun.com/QandA/1745781350a91207.html