Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4.x: Document CDI based mocking for unit tests #8339

Closed
4 tasks
romain-grecourt opened this issue Feb 6, 2024 · 3 comments · Fixed by #8787
Closed
4 tasks

4.x: Document CDI based mocking for unit tests #8339

romain-grecourt opened this issue Feb 6, 2024 · 3 comments · Fixed by #8787
Assignees
Labels
4.x Version 4.x cors Related to CORS support docs MP P3 testing

Comments

@romain-grecourt
Copy link
Contributor

romain-grecourt commented Feb 6, 2024

Environment Details

  • Helidon Version: 4.0.4
  • Helidon MP

Problem Description

Mockito's @Mock does not work with @HelidonTest.


Consider the following JAXRS resource and CDI bean:

@ApplicationScoped
public class FooService {

    public String getFoo() {
        return "foo";
    }
}

@Path("/foo")
public class FooResource {

    @Inject
    private FooService fooService;

    @GET
    public String getFoo() {
        return fooService.getFoo();
    }
}

A test that combines both @HelidonTest and @Mock does not work:

@HelidonTest
@ExtendWith(MockitoExtension.class)
class AcmeTest {
 
    @Inject
    private WebTarget target;

    @Mock
    private AcmeService acmeService;

    @Test
    void testFoo() {
        when(acmeService.getFoo()).thenReturn("bar");

        Response response = target.path("/foo").request().get();

        assertThat(response.getStatus(), is(200));
        assertThat(response.readEntity(String.class), is("bar"));
     }
}

Current solution

Instead, we need to use CDI to provide an alternative. This solution works with Helidon 2.x, 3.x and 4.x.

@HelidonTest
@Priority(1) // required for @Alternative
class FooTest {

    @Inject
    private WebTarget target;

    private FooService fooService;

    @BeforeEach
    void initMock() {
        // set fooService to a new mock before each test
        // use real methods for default answers
        fooService = Mockito.mock(FooService.class, InvocationOnMock::callRealMethod);
    }

    @Produces
    @Alternative
    FooService mockFooService() {
        // This makes FooResource inject our mock instead of the default singleton
        return fooService;
    }

    @Test
    void testMockedFoo() {
        // test the modified behavior
        when(fooService.getFoo()).thenReturn("bar");

        Response response = target.path("/foo").request().get();

        assertThat(response.getStatus(), is(200));
        assertThat(response.readEntity(String.class), is("bar"));
    }

    @Test
    void testFoo() {
        // test the non modified behavior
        Response response = target.path("/foo").request().get();

        assertThat(response.getStatus(), is(200));
        assertThat(response.readEntity(String.class), is("foo"));
    }
}

pom.xml:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.8.0</version>
    <scope>test</scope>
</dependency>

Action items:

  • document how to setup mocks using CDI producer methods (I.e. the example above)
  • Investigate if there is a way to make HelidonTest and MockitoExtension work together.
  • Investigate if we can provide an annotation to setup a mock as an alternative
  • Investigate if we should support other use-cases (e.g. @org.mockito.Spy, @org.mockito.Captor)

Example of a custom annotation to polish the example above:

@HelidonTest
class FooTest {

    // Creates a new mock instance for each test
    // and expose it as an alternative
    @io.helidon.microprofile.testing.junit5.Mock
    private FooService fooService;

    @Test
    void testMockedFoo() {
        // same as above
    }
}
@romain-grecourt romain-grecourt added MP cors Related to CORS support testing 4.x Version 4.x labels Feb 6, 2024
@jbescos
Copy link
Member

jbescos commented Apr 17, 2024

I would do it in the Spring way, it will be more intuitive for users (this is what you said in FooTest). There is an issue for this:
#7694

We can declare a field annotated with @MockBean and that will be mocked by Mockito and set it in CDI container, so every dependency to that will contain the mock instance.

@tvallin tvallin removed their assignment Apr 17, 2024
@jbescos jbescos self-assigned this Apr 17, 2024
@romain-grecourt
Copy link
Contributor Author

@jbescos This issue is about documenting what's currently doable (as of 4.0.7) ; the addition of a new new annotation to support mocked beans should be done in a separate issue.

@jbescos
Copy link
Member

jbescos commented Apr 18, 2024

@jbescos This issue is about documenting what's currently doable (as of 4.0.7) ; the addition of a new new annotation to support mocked beans should be done in a separate issue.

OMG, I assigned to myself a documentation issue :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4.x Version 4.x cors Related to CORS support docs MP P3 testing
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

4 participants