Search code examples
unit-testingmockitocdihelidon

Replace a bean by a mock in Helidon test


I have a Helidon application and I would like to test (part of) it.

My test is annotated with @HelidonTest, and now I would like to replace one bean by a mock (and configure this mock, use all other beans as they are found, but with the mock injected).

I did figured out how to:

  • Replace one bean by a test implementation (separate class): By annotating the test implementation class with @Priority(1) and @Alternative and supply it by annotating the test with @AddBean(MyBeanTestImpl.class).
    • But I can not create a mock (with Mockito) as an individual class.
  • Produce a mock(MyBean.class): By creating a producer method and annotate it with @Produces:
    • But it clashes with the real bean and gives: "WELD-001409: Ambiguous dependencies for type..."
    • When I annotate it also with @Alternative it is simply ignored.
    • I can not annotate it with @Priority(1), because this annotation can only be applied to types and parameters.

Any idea how I can replace one bean by a mock?


Solution

  • I tried setter injection to manually inject mock beans.

    Class under test

    @ApplicationScoped
    public class SomeService {
        private ExternalService externalService;
    
        @Inject
        public void setExternalService(ExternalService externalService) {
            this.externalService = externalService;
        }
    
        public String doSomething() {
            return externalService.getData();
        }
    }
    

    Test Class

    @HelidonTest
    class SomeServiceTest {
        @Inject
        private SomeService someService;
    
        private ExternalService externalService;
    
        @BeforeEach
        void setUp() {
            externalService = Mockito.mock(ExternalService.class);
            someService.setExternalService(externalService);
        }
    
        @Test
        void doSomething() {
            Mockito.when(externalService.getData())
                    .thenReturn("Mock data");
            String actual = someService.doSomething();
            Assertions.assertEquals("Mock data", actual);
        }
    }
    

    There are also methods to mock a whole bean by mocking the constructor as well. For that, we have to make use of @Observes annotation

    @HelidonTest
    public abstract class HelidonTestHelper {
    
    
        private MockedConstruction<ExternalService> mockedConstruction;
    
        void init(@Priority(1) @Observes @Initialized(ApplicationScoped.class) ContainerInitialized containerInitialized) {
            mockedConstruction = Mockito.mockConstruction(ExternalService.class);
            //mock common beans here. This will be executed once application scope is loaded.
        }
        void onStop(@Priority(1) @Observes @Destroyed(ApplicationScoped.class) ContainerShutdown containerShutdown) {
            //do cleanup here if needed.
            mockedConstruction.closeOnDemand();
        }
    }
    

    Once the above is done, instead of helidon test, you can extend the helper class we created.

        class SomeServiceTest extends HelidonTestHelper {
            @Inject
            private SomeService someService;
        
            @Inject //this will be a mock 
            private ExternalService externalService;
            
            @Test
            void doSomething() {
                Mockito.when(externalService.getData())
                        .thenReturn("Mock data");
                String actual = someService.doSomething();
                Assertions.assertEquals("Mock data", actual);
            }
    
          }