weld / weld-testing

Set of test framework extensions (JUnit 4, JUnit 5, Spock) to enhance the testing of CDI components via Weld. Supports Weld 5.
http://weld.cdi-spec.org/
Apache License 2.0
102 stars 30 forks source link

How to test MP Config ? #216

Closed nicolasduminil closed 2 months ago

nicolasduminil commented 2 months ago

Hello,

Using weld-junit5:4.0.3.Final, I'm trying to test MP Config as follows:

@EnableAutoWeld
public class MpConfigIT
{
  @ConfigProperty(name = "base_uri/mp-rest/url")
  String baseURI;

  @Test
  public void testMpConfig()
  {
    assertThat(baseURI).isNotNull();
  }

  @Inject
  private Foo foo;

  @Test
  void testFoo() 
  {
    assertThat(foo).isNotNull();
    assertThat(foo.getBar()).isEqualTo("baz");
  }

Here, the property named base_uri/mp-rest/url is defined in the META-INF/microprofile-config.properties file.

I expect that both tests are successful but only the 2nd one succeeds, the 1st one raises:

[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Failures: 
[ERROR]   MpConfigIT.testMpConfig:20 
Expecting actual not to be null
[INFO] 
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0

Is that supposed to work at all ? If yes, what am I doing wrong ? If no, how could I use Weld to test MP stuff ?

Many thanks in advance.

Nicolas

manovotn commented 2 months ago

Hello

First of all, Weld-junit is only handling standard CDI injection points meaning they will require @Inject annotation. Anything else won't be picked up (such are standard CDI rules). So the IP would have to look like this:

  @Inject
  @ConfigProperty(name = "base_uri/mp-rest/url")
  String baseURI;

Now, I don't know your project setup, but Weld alone won't satisfy config injection points; implementation of MP Config do that (such as SmallRye) so you'd need to include one and set up the Weld container to either discover those implementation classes or manually register them via annotations.

If you need more control over how Weld is initialized, I suggest you take a look at the non-auto extension where you can provide WeldInitializer or whole Weld container object with arbitrary setup.

nicolasduminil commented 2 months ago

@manovotn : Thanks for your help. Adding the @Inject annotation in front of the existent one (ConfigProperty) doesn't change anything. The implementation of MP Config that I'm using is the Wildfly one and the required dependencies are included, of course, otherwise the code wouldn't have even compiled. As for setting up the Weld container, my understanding is that I don't have to do anything, as long as I'm using Wildfly. And I don't think I'd need more control over the Weld container in order to run such a trivial example. I could share a simple reproducer with you if you can help.

mkouba commented 2 months ago

And I don't think I'd need more control over the Weld container in order to run such a trivial example.

As Matej already pointed out the goal of weld-junit is to provide a simple tool to test/mock CDI components. It does not start anything but the CDI container. And it does not register any additional CDI beans.

In order to make the @ConfigProperty injection points satisfied you'll need to:

nicolasduminil commented 2 months ago

@manovotn : Please find here a simple reproducer, if you have time. To reproduce, just run mvn package failsafe:integration-test after having checked out the project from GitHub. Many thanks in advance.

manovotn commented 2 months ago

And I don't think I'd need more control over the Weld container in order to run such a trivial example.

Martin's comment explains what I meant - compile dependencies don't equal what's present in the CDI test container.

Adding the @Inject annotation in front of the existent one (ConfigProperty) doesn't change anything.

It does; in your case it should result in an exception starting the test. Something like:

IllegalArgument WELD-001408: Unsatisfied dependencies for type...

This alone already tell you Weld-junit recognized the injection point, tried to handle it but something went wrong.

The implementation of MP Config that I'm using is the Wildfly one

Ok, WildFly has no standalone implementation. As far as I can tell, it uses SmallRye impl; see their pom.xml. When you run Weld-junit, you only start a minimal CDI container with whatever beans you give it (here are details on how what is done in the auto extension). It will not scan your classpath and will not interact or use WFLY in any way. If you are looking at full blown WFLY testing, Arquillian is probably the framework you are after.

And I don't think I'd need more control over the Weld container in order to run such a trivial example. Please find here a simple reproducer, if you have time. To reproduce, just run mvn package failsafe:integration-test after having checked out the project from GitHub.

If your own test/app provides all the beans needed for injection then sure. That's the case for your Foo bean and that's why it works. However, you don't provide a bean for the config injection point and Weld has no way of knowing where and how to get a bean for that, hence the failure.

Looking at it, SR config tests are actually using Weld-junit, so it should definitely be doable although they are using declarative approach. See for instance this test. It seems the bulk of the work is done by their CDI extension, so perhaps if you add @AddExtensions(io.smallrye.config.inject.ConfigExtension) to your test, it might work.

nicolasduminil commented 2 months ago

@mkouba : Not sure what you're trying to explain here.

Provide a CDI bean that would satisfy such an injection point ...

I don't see in the link you've provided anything which could have any relationship with my case consisting in using MP Config in Weld based integration tests.

Start an MP Config implementation and its CDI integration ...

Based on the example in the link, my interpretation is that what you suggest is something like this:

  ...
  SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);

  @Inject
  @ConfigProperty(name = "base_uri/mp-rest/url")
  ...

This raises the same exception:

WELD-001408: Unsatisfied dependencies for type String with qualifiers @ConfigProperty ...

Did you notice the reproducer ? Wouldn't it be much simpler to just mention what exactly should I do in this 10 lines code such that to get things working instead of providing links to example of 500 lines ?

nicolasduminil commented 2 months ago

@manovotn

... if you add @AddExtensions(io.smallrye.config.inject.ConfigExtension) to your test, it might work ...

Yeah, it might. We'll never know as the mentioned class doesn't exist in io.smallrye.config:smallrye-config:3.9.1 and hence, I don't know where to find it.

But I even don't understand why should I chase around for classes as far as I have MP based components running without anything else than microprofile-config-api. I can test them in testcontainers or Arquillian so why do I need to make things so difficult by directly using Smallrye, which I'm not supposed to, given that I'm running in Wildfly 33.0 which integrates it ?

So my question is simple: could anyone amend this 10 lines reproducer that I provided such that to get it running ? If not, that's fine, I don't even have to use Weld, it was just an idea to compare it with other integration test methods.

Many thanks in advance.

manovotn commented 2 months ago

Yeah, it might. We'll never know as the mentioned class doesn't exist in io.smallrye.config:smallrye-config:3.9.1 and hence, I don't know where to find it.

Here's the nonexisting class - https://github.com/smallrye/smallrye-config/blob/3.9.1/cdi/src/main/java/io/smallrye/config/inject/ConfigExtension.java

The dependency you are looking for is:

    <dependency>
      <groupId>io.smallrye.config</groupId>
      <artifactId>smallrye-config</artifactId>
      <version>3.9.1</version>
      <scope>test</scope>
    </dependency>

And your test needs to look like this:

package fr.simplex_software.workshop.weld_cdi.tests;

import fr.simplex_software.worksho.weld_cdi.*;
import io.smallrye.config.inject.ConfigExtension;
import jakarta.inject.*;
import org.eclipse.microprofile.config.inject.*;
import org.jboss.weld.junit5.auto.*;
import org.junit.jupiter.api.*;

import static org.assertj.core.api.Assertions.*;

@EnableAutoWeld
@AddExtensions(ConfigExtension.class) // just add SR CDI extension which will register their components and make config injection work
public class MpConfigIT
{
  @Inject
  @ConfigProperty(name = "base_uri/mp-rest/url")
  String baseURI;

  @Test
  public void testMpConfig()
  {
    assertThat(baseURI).isNotNull();
  }

  @Inject
  private Foo foo;

  @Test
  void testJakartaInject() {
    assertThat(foo).isNotNull();
    assertThat(foo.getBar()).isEqualTo("baz");
  }
}

so why do I need to make things so difficult by directly using Smallrye, which I'm not supposed to, given that I'm running in Wildfly 33.0 which integrates it ?

Your test is not running WildFly, that's why.

Did you notice the reproducer ? Wouldn't it be much simpler to just mention what exactly should I do in this 10 lines code such that to get things working instead of providing links to example of 500 lines ?

No, I vastly prefer to try and provide understanding of why and how things work instead of giving out pieces of code that are just blindly copied :)

nicolasduminil commented 2 months ago

I confirm that doing the mentioned modifications, the issue is fixed. I have to apologize for my previous comment saying that the artifact io.smallrye.config:smallrye-config:3.9.1 didn't contain the io.smallrye.config.inject.ConfigExtension class. As a matter of fact it does but, further to a misconfiguration of my IDE it wasn't seen. Running Maven on command line works as expected. By the way, you might want to add this test case to your documentation as it could save the day of other persons as well while avoiding you the disagreement to reply to issues. :-) Many thanks again for your help and support.