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: Ability to Inject MockBeans in Helidon #7694 #8674

Merged
merged 13 commits into from May 17, 2024

Conversation

jbescos
Copy link
Member

@jbescos jbescos commented Apr 18, 2024

Description

#7694

Documentation

This feature provides a simple mechanism for replacing CDI bean with mock instances created with Mockito.
It is similar to Spring's @MockBean, in fact it uses the same name.

The introduced API consist of a new Maven module io.helidon.microprofile.testing:helidon-microprofile-testing-mocking that provides one annotation @MockBean.

The annotation is designed to be used on fields of test classes annotated with @HelidonTest, the implementation relies only on CDI thus it works with either JUnit or TestNG.

The annotation has a parameter "answer" that can be used to set the default answer for the mocked beans.

See a snippet below:

@HelidonTest
@AddBean(TestMockBeanAnswer.Resource.class)
@AddBean(TestMockBeanAnswer.Service.class)
class TestMockBeanAnswer {

    @MockBean(answer = Answers.CALLS_REAL_METHODS)
    private Service service;
    @Inject
    private WebTarget target;

    @Test
    void injectionTest() {
        String response = target.path("/test").request().get(String.class);
        assertThat(response, is("Not Mocked"));
        Mockito.when(service.test()).thenReturn("Mocked");
        response = target.path("/test").request().get(String.class);
        assertThat(response, is("Mocked"));
    }

    @Path("/test")
    public static class Resource {

        @Inject
        private Service service;

        @GET
        public String test() {
            return service.test();
        }
    }

    static class Service {

        String test() {
            return "Not Mocked";
        }

    }
}

The mocked beans of a test class can be customized with a CDI producer method.
E.g.

@Produces
MockSettings mockSettings() {
    return Mockito.withSettings().defaultAnswer(Answers.RETURNS_DEFAULTS);
}

@jbescos jbescos marked this pull request as draft April 18, 2024 11:50
@oracle-contributor-agreement oracle-contributor-agreement bot added the OCA Verified All contributors have signed the Oracle Contributor Agreement. label Apr 18, 2024
@jbescos
Copy link
Member Author

jbescos commented Apr 18, 2024

Setting it as a draft for now. I have a couple of CDI questions:

Question 1:

Currently @MockTest requires also @Inject:

    @Inject
    @MockBean
    private Service service;

Is there any way to not require @Inject?.

Question 2:

It does not work if the bean already exists in CDI. For example, this will not work because the service will be defined twice (you need to remove @AddBean(Service.class)):

@HelidonTest
@AddBean(Service.class)
public class TestMockBean {

    @Inject
    @MockBean
    private Service service;

I didn't find how to replace the existing bean defined by the mocked one.

Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
@romain-grecourt
Copy link
Contributor

romain-grecourt commented Apr 18, 2024

I didn't find how to replace the existing bean defined by the mocked one.

I'm not a CDI expert, but can you set the mocked bean as an alternative ? That's how the current solution (#8339) works.

@jbescos
Copy link
Member Author

jbescos commented Apr 19, 2024

I didn't find how to replace the existing bean defined by the mocked one.

I'm not a CDI expert, but can you set the mocked bean as an alternative ? That's how the current solution (#8339) works.

I think that requires a producer, and I cannot use a producer because you need to know the type in advance. I will try it further. Do you know who is expert on this?, I bet for @tomas-langer

@jbescos
Copy link
Member Author

jbescos commented Apr 22, 2024

Question 1 is answered in this commit: 57ac20b

@jbescos jbescos force-pushed the issue7694 branch 4 times, most recently from 319343b to b7314f4 Compare April 23, 2024 05:09
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
@jbescos
Copy link
Member Author

jbescos commented Apr 23, 2024

Question 2 solved here: e0cd11b
You were right @romain-grecourt with the alternatives, thanks.

Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
@jbescos jbescos force-pushed the issue7694 branch 2 times, most recently from 677558f to 1087fb3 Compare April 23, 2024 08:06
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
@jbescos jbescos force-pushed the issue7694 branch 2 times, most recently from 1153bc3 to 48461cd Compare April 23, 2024 13:40
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
@jbescos jbescos marked this pull request as ready for review April 24, 2024 05:29
@jbescos
Copy link
Member Author

jbescos commented Apr 24, 2024

For TestNG it seems we cannot use the @Inject in constructor, so the @MockBean applies only in fields.

For example, this simple test:

@HelidonTest
class TestMockBeanField {

    private final WebTarget target;

    @Inject
    public TestMockBeanField(WebTarget target) {
        this.target = target;
    }

    @Test
    void test() {
    }
}

It fails with:

org.testng.TestNGException: 
An error occurred while instantiating class io.helidon.microprofile.tests.testing.testng.TestMockBeanField. Check to make sure it can be instantiated/accessed.
	at org.testng.internal.objects.SimpleObjectDispenser.createInstance(SimpleObjectDispenser.java:113)
	at org.testng.internal.objects.SimpleObjectDispenser.dispense(SimpleObjectDispenser.java:40)
	at org.testng.internal.objects.GuiceBasedObjectDispenser.dispense(GuiceBasedObjectDispenser.java:28)
	at org.testng.internal.ClassImpl.getDefaultInstance(ClassImpl.java:106)
	at org.testng.internal.ClassImpl.getInstances(ClassImpl.java:136)
	at org.testng.TestClass.getInstances(TestClass.java:129)
	at org.testng.TestClass.initTestClassesAndInstances(TestClass.java:109)
	at org.testng.TestClass.init(TestClass.java:101)
	at org.testng.TestClass.<init>(TestClass.java:66)
	at org.testng.TestRunner.initMethods(TestRunner.java:483)
	at org.testng.TestRunner.init(TestRunner.java:356)
	at org.testng.TestRunner.init(TestRunner.java:309)
	at org.testng.TestRunner.<init>(TestRunner.java:184)
	at org.testng.SuiteRunner$DefaultTestRunnerFactory.newTestRunner(SuiteRunner.java:652)
	at org.testng.SuiteRunner.init(SuiteRunner.java:224)
	at org.testng.SuiteRunner.<init>(SuiteRunner.java:116)
	at org.testng.TestNG.createSuiteRunner(TestNG.java:1375)
	at org.testng.TestNG.createSuiteRunners(TestNG.java:1349)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1191)
	at org.testng.TestNG.runSuites(TestNG.java:1114)
	at org.testng.TestNG.run(TestNG.java:1082)
	at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:155)
	at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeSingleClass(TestNGDirectoryTestSuite.java:102)
	at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:91)
	at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:137)
	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385)
	at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162)
	at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507)
	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495)

Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
Copy link
Contributor

@romain-grecourt romain-grecourt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks nice so far.
We need to update the docs as well.

Copy link
Member

@tomas-langer tomas-langer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a great feature, but the hard dependency on mockito is something we probably do not desire.
It seems to me that the whole feature is implemented as a CDI extension. Can we maybe create a new module (depending on mockito), that would add the CDI extension and the annotation, and that could be used with both JUnit5 and testNG extension, because it is "just" a CDI portable extension?
i.e. helidon-microprofile-testing-mock-beans?

@jbescos jbescos requested a review from barchetta May 7, 2024 12:44
@jbescos jbescos added the dependencies Pull requests that update a dependency file label May 7, 2024
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
Copy link
Member

@tomas-langer tomas-langer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few small things.

microprofile/testing/mock-beans/pom.xml Outdated Show resolved Hide resolved
Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
Copy link
Member

@tomas-langer tomas-langer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good now, let's wait for library approval.

Signed-off-by: Jorge Bescos Gascon <jorge.bescos.gascon@oracle.com>
barchetta
barchetta previously approved these changes May 15, 2024
Copy link
Member

@barchetta barchetta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dependencies look good to me. Thanks for all the work on this!

Copy link
Contributor

@romain-grecourt romain-grecourt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, please update the PR description to fill-in the documentation section:

  • describe the feature (what it solves)
  • describe the API (annotation + new GAV)
  • add sample snippet

Let's also file an issue to create the actual docs for this feature.

@jbescos
Copy link
Member Author

jbescos commented May 16, 2024

Looks good, please update the PR description to fill-in the documentation section:

* describe the feature (what it solves)

* describe the API (annotation + new GAV)

* add sample snippet

Let's also file an issue to create the actual docs for this feature.

Do we re-use this issue?: #8339

I didn't work in any documentation issue before, is there any guide?

@romain-grecourt
Copy link
Contributor

Do we re-use this issue?: #8339

Let's file a separate issue as #8339 has more to it than strictly document this new feature.
If we want to proceed with #8338 we have to figure out where it fits in the docs.

I didn't work in any documentation issue before, is there any guide?

No, however adding one more document is relatively easy as there is a lot of precedence.


The bullet items I provided was guidance not a template. It should read like a short documentation of the feature not like a form !

@jbescos
Copy link
Member Author

jbescos commented May 17, 2024

This is the new issue:
#8772

Are there more changes required in this PR or can it be approved/merged. I guess the documentation changes will be submitted in the new issue I created.

Copy link
Contributor

@romain-grecourt romain-grecourt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, nice work.
I've updated the description.

@romain-grecourt romain-grecourt merged commit 4f8043d into helidon-io:main May 17, 2024
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Pull requests that update a dependency file OCA Verified All contributors have signed the Oracle Contributor Agreement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants