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
101 stars 30 forks source link

[QUESTION] Unable to inject (field level) bean in unit test #119

Closed x80486 closed 3 years ago

x80486 commented 3 years ago

I have a small (Java SE 11.x) project where I'm testing CDI with Weld. I have these dependencies (among others but these are the relevant ones for this issue):

implementation("org.jboss.weld.se:weld-se-core:4.0.0.Final")

runtimeOnly("javax:javaee-api:8.0.1") // Can remove this if weld-se-shaded is used instead

testImplementation("org.jboss.weld:weld-junit5:2.0.2.Final")

I have a simple POJO annotated with @Singleton and that's what I'm trying to inject in the test class:

import jakarta.inject.Inject;
import org.acme.service_layer.persistence.SpellBookDao;
import org.assertj.core.api.Assertions;
import org.jboss.weld.junit5.EnableWeld;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.util.UUID;

@EnableWeld
final class SpellBookDaoTest {
  @Inject
  private SpellBookDao dao;

  @Test
  void findByName_WhenSpellBookExists() {
    final var optional = dao.findByName("The Dark Lord Ascending");
    Assertions.assertThat(optional)
        .hasValueSatisfying(it -> {
          Assertions.assertThat(it.getId()).isEqualTo(UUID.fromString("715811c9-ae11-41ec-8652-671fd88cd2a0"));
          Assertions.assertThat(it.getName()).isEqualTo("The Dark Lord Ascending");
          Assertions.assertThat(it.getSpells()).isEmpty(); // TODO: Won't work because the session will be closed at this point O:)
          Assertions.assertThat(it.getWizards()).isEmpty();
          // ...
          Assertions.assertThat(it.getVersion()).isEqualTo(0L);
        });
  }
}

According to the documentation this should work in such way, but it always fails with:

SpellBookDaoTest > findByName_WhenSpellBookExists() FAILED
    org.jboss.weld.exceptions.DeploymentException: WELD-001408: Unsatisfied dependencies for type SpellBookDao with qualifiers @Default
      at injection point [BackedAnnotatedField] @Inject private org.acme.service_layer.persistence.SpellBookDaoTest.dao
      at org.acme.service_layer.persistence.SpellBookDaoTest.dao(SpellBookDaoTest.java:0)
        at org.jboss.weld.bootstrap.Validator.validateInjectionPointForDeploymentProblems(Validator.java:378)
        at org.jboss.weld.bootstrap.Validator.validateInjectionPoint(Validator.java:290)
        at org.jboss.weld.bootstrap.Validator.validateGeneralBean(Validator.java:143)
        at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:164)
        at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:526)
        at org.jboss.weld.bootstrap.Validator.validateBeans(Validator.java:512)
        at org.jboss.weld.bootstrap.Validator.validateDeployment(Validator.java:487)
        at org.jboss.weld.bootstrap.WeldStartup.validateBeans(WeldStartup.java:496)
        at org.jboss.weld.bootstrap.WeldBootstrap.validateBeans(WeldBootstrap.java:93)
        at org.jboss.weld.environment.se.Weld.initialize(Weld.java:815)
        ...and many more here 🤣 

I was wondering if this is a bug with this library or if there is anything else we should do to make it work? Notice that I'm trying to inject the fields in the class by using @Inject avoiding the use of weld.select(...).get().

This is the original (obligatory) question, with more details, in StackOverflow. Additionally, I have a project here that can be used to test this, just in case 😇

mkouba commented 3 years ago

Hm, it seems like your project depends on org.jboss.weld:weld-junit5:2.0.2.Final which brings in org.jboss.weld.se:weld-se-core:3.1.6.Final and also org.jboss.weld.se:weld-se-shaded:4.0.0.Final which is a shaded artifact that contains all Weld classes needed for Java SE: https://github.com/x80486/javase-cdi-weld/blob/master/build.gradle#L38-L40. And so the classpath probably contains multiple versions of the Weld classes (I have no idea how gradle handles this situation). In any case, the problem might be that Weld 3 is using Java EE APIs whereas Weld 4 depends on Jakarta EE APIs, i.e. the @jakarta.inject.Singleton declared on SpellBookDao might be just ignored because Weld 3 classes might be loaded first.

manovotn commented 3 years ago

Hi,

you're seeing this issue because weld-junit is currently built on top of javax packages and therefore Weld 3.x. It doesn't have a jakarta-friendly version yet.

Martin is right that you are mixing versions, but even if you used Weld 4 everywhere, this wouldn't fly. What you could do in the meantime is use Java/Jakarta EE 8 with javax packages and use Weld 3. Then switch once we ship new major version of weld-junit.

Anyway, we should fix that ASAP, I was actually thinking about it recently but didn't get to it yet, thanks for this reminder! Probably the only sensible approach is the same as with Weld - have two separate versions, each using a different namespace. WDYT @mkouba?

x80486 commented 3 years ago

All right! Thanks for the tips! 🍹 — dangling dependencies are always a hassle.

Wouldn't be better if Weld publishes a BOM and we just need to import it as such, then the rest of the dependencies will get resolved correctly? Something like this:

implementation(enforcedPlatform("org.jboss.weld:weld-core-bom:4.0.0.Final"))

implementation("org.jboss.weld.se:weld-se-shaded")

testImplementation("org.jboss.weld:weld-junit5")
manovotn commented 3 years ago

Weld already has a BOM which allows you to align dependencies inside Weld and CDI. However, it doesn't contain any information about weld-junit.

I am not sure how much sense it makes to put it there - mainly because I cannot tell how common it is to use weld-junit where you use Weld itself. Plus it basically introduces a circular dependency between these projects which I am pretty much against. But I see where you're coming from.

x80486 commented 3 years ago

Martin is right that you are mixing versions, but even if you used Weld 4 everywhere, this wouldn't fly. What you could do in the meantime is use Java/Jakarta EE 8 with javax packages and use Weld 3. Then switch once we ship new major version of weld-junit.

I tried this approach and still get the same results. I guess I'm going to wait for the new update and will take it from there.

manovotn commented 3 years ago

I tried this approach and still get the same results. I guess I'm going to wait for the new update and will take it from there.

That really should work, you probably have some misconfiguration is the test somewhere (like beans not picked up etc).

manovotn commented 3 years ago

@x80486 version 3.0.0.Final should land in Central any time soon. Feel free to report back on whether you solved your issue :-)

x80486 commented 3 years ago

Probably I have the wrong assumption(s) about how this work, but I'm getting the same results.

Anyway, this is what I'm thinking on how this is should work based on this guide:

Any field in that test class annotated with @jakarta.inject.Singleton should be injected properly since @EnableAutoWeld is going to take care about the bootstrapping and discovering.

I updated the project, just in case you want to take a look

manovotn commented 3 years ago

@x80486 I took a look at your repo and here are some hints:

Last but not least, in your case the @EnableAutoWeld approach doesn't do much and you need to be more declarative in which case there is little difference between doing it this way and using just @EnableWeld + @WeldSetup but in the end both approaches are viable and it is entirely up to you.

That's about all I can do to help :)

x80486 commented 3 years ago

Wow...it works now! 🍹

I need to add the classes to the @AddBeanClasses as you suggested — and also include the @ActivateScopes. I was assuming it wasn't needed and the entire classpath was scanned. Anyway, it has to be selectively configured...not a big deal for the benefits that the library provides!

Thanks again for the tips and the effort! 🥇

manovotn commented 3 years ago

Oh yea, I forgot to mention the scope as well. BTW you can either enable it for the whole duration of the test (which is via @ActivateScopes) or you can do it for the duration of certain bean method via @ActivateRequestContext as described here in the specification.

manovotn commented 3 years ago

I was assuming it wasn't needed and the entire classpath was scanned.

You could actually achieve this as well, but it is not the default behavior :-)