quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.58k stars 2.63k forks source link

CDI not working with @QuarkusComponentTest #41200

Closed octopus-prime closed 3 months ago

octopus-prime commented 3 months ago

Describe the bug

Using MapStruct

@Mapper
public interface FooMapper {
}

and QuarkusComponentTest

@QuarkusComponentTest
class FooMapperTest {

    @Inject
    FooMapper fooMapper;

    @Test
    void test() {
    }
}

does not work.

Expected behavior

The generated FooMapperImpl should be injected.

Actual behavior

java.lang.NullPointerException: Cannot invoke "io.quarkus.arc.InjectableBean.getKind()" because the return value of "io.quarkus.arc.InstanceHandle.getBean()" is null

How to Reproduce?

Run the test

Output of uname -a or ver

No response

Output of java -version

No response

Quarkus version or git rev

No response

Build tool (ie. output of mvnw --version or gradlew --version)

No response

Additional information

If you replace @QuarkusComponentTest by @QuarkusTest the test works fine.

quarkus-bot[bot] commented 3 months ago

/cc @Ladicek (arc), @manovotn (arc), @mkouba (arc)

mkouba commented 3 months ago

I'm sorry but I have no idea what the MapStruct Quarkus integration looks like. In any case, the QuarkusComponentTest does not involve any extensions, which means that no build steps are executed at all. In other words, it works with "pure" CDI components only. See also the Lifecycle chapter in the docs.

If you replace @QuarkusComponentTest by @QuarkusTest the test works fine.

Indeed, @QuarkusTest runs the full build of your app.

What's weird that you get a NPE - this should never happen. You should get a reasonable error message instead. Could you pls share a simple reproducer app?

octopus-prime commented 3 months ago

Okay, let's try without MapStruct.

public interface FooMapper {
}
@ApplicationScoped
public class FooMapperImpl implements FooMapper {
}
@QuarkusComponentTest
class FooMapperTest {

    @Inject
    FooMapper fooMapper;

    @Test
    void test() {
    }
}

Same result: java.lang.NullPointerException: Cannot invoke "io.quarkus.arc.InjectableBean.getKind()" because the return value of "io.quarkus.arc.InstanceHandle.getBean()" is null

mkouba commented 3 months ago

Okay, let's try without MapStruct.

public interface FooMapper {
}
@ApplicationScoped
public class FooMapperImpl implements FooMapper {
}
@QuarkusComponentTest
class FooMapperTest {

    @Inject
    FooMapper fooMapper;

    @Test
    void test() {
    }
}

Same result: java.lang.NullPointerException: Cannot invoke "io.quarkus.arc.InjectableBean.getKind()" because the return value of "io.quarkus.arc.InstanceHandle.getBean()" is null

I'll take a look in a moment...

mkouba commented 3 months ago

Okay, let's try without MapStruct.

public interface FooMapper {
}
@ApplicationScoped
public class FooMapperImpl implements FooMapper {
}
@QuarkusComponentTest
class FooMapperTest {

    @Inject
    FooMapper fooMapper;

    @Test
    void test() {
    }
}

Same result: java.lang.NullPointerException: Cannot invoke "io.quarkus.arc.InjectableBean.getKind()" because the return value of "io.quarkus.arc.InstanceHandle.getBean()" is null

@octopus-prime Ok, so you'll need to use @QuarkusComponentTest(FooMapperImpl.class) in order to make this work. The reason is that QuarkusComponentTest is not able to derive the implementation from the test class.

The initial set of components under test contains all the types of all fields annotated with @Inject and all types of parameters of test methods that are not annotated with @InjectMock or @SkipInject. Furthermore, the static nested classes declared on the test class are components too.

Now back to your snippet - FooMapperImpl is not in the initial set of components. But the @Inject FooMapper field "expects" a real component. I fixed the NPE in https://github.com/quarkusio/quarkus/pull/41205.

octopus-prime commented 3 months ago

Okay. Thank you for pointing this out.