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

Lost active contexts when using static weld #167

Closed shishovsa closed 10 months ago

shishovsa commented 10 months ago

When I using static weld object for creating WeldInitiator, active contexts was lost after two tests.

[ERROR] Errors: 
[ERROR]   WeldTest.doTest3:42 » ContextNotActive WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped
[ERROR]   WeldTest.doTest4:47 » ContextNotActive WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped

But when I using WeldInitiator.from(<classes>) all tests works fine.

The version of weld-junit is 2.0.2.Final, weld is 3.1.9 (Final), junit is 5.4.2.

@RequestScoped
class ServiceFoo {
    void run() {
    }
}

@ExtendWith(WeldJunit5Extension.class)
class WeldTest {

    @Inject
    protected ServiceFoo serviceFoo;

    static final Weld weld = WeldInitiator.createWeld();
    static {
        weld.addBeanClass(ServiceFoo.class);
    }

    @WeldSetup
    public WeldInitiator weldInitiator =
            WeldInitiator.from(weld)
            //WeldInitiator.from(ServiceFoo.class)
                    .activate(RequestScoped.class)
                    .build();

    @Test
    void doTest1() {
        serviceFoo.run();
    }

    @Test
    void doTest2() {
        serviceFoo.run();
    }

    @Test
    void doTest3() {
        serviceFoo.run();
    }

    @Test
    void doTest4() {
        serviceFoo.run();
    }
}
manovotn commented 10 months ago

Hello @shishovsa,

I took some time debugging this and below are my findings.

In your scenario you are using Weld-junit in a TestInstance.Lifecycle#PER_METHOD mode (which is junit's default) meaning that Weld will attempt to start/shutdown container for each test method.

    static final Weld weld = WeldInitiator.createWeld();
    static {
        weld.addBeanClass(ServiceFoo.class);
    }

The above lines result in a configured Weld builder stored statically. That means you are now carrying a configured builder instance between those test methods.

But you are also using a non-static setup to further configure Weld instance:

            WeldInitiator.from(weld)
                    .activate(RequestScoped.class)
                    .build();

Now, this code applies further configuration to Weld object and is invoked repeatedly for each test method again - reapplying whatever configuration you are adding. This causes issues because there's an underlying CDI extension responsible for context activations that ends up being registered with the same static Weld object repeatedly.

Maybe I am missing something but the setup you have seems rather weird - I recommend that you change it. You could do one of the following:

manovotn commented 10 months ago

I understand the underlying issue here is hard to spot for users so I've sent a PR that adds some documentation - https://github.com/weld/weld-testing/pull/168

shishovsa commented 10 months ago

Thank you @manovotn !

Maybe I am missing something but the setup you have seems rather weird...

Basically idea was to create abstract test class with all setup: weld, mocks, dockers. Then extend simple test classes with only test logic (integration tests).

To prevent unwanted long-run initialization all services was possibly static and PER_CLASS. In @AfterEach-method system was clearing.

My trouble is that WeldInitiator was not static field. Alternatively case with full configuration in the @WeldSetup also works fine. And maybe it is better to do Weld not static and use PER_METHOD to reinitialize request scoped beans from test to test...

Thank you again!

A bit complex example

```java public class SettingsManager { public String getConnectionString(){ return ""; } } @RequestScoped class ServiceFoo { @Inject private SettingsManager settingsManager; void run() { System.out.println(settingsManager.getConnectionString()); } } @ExtendWith(WeldJunit5Extension.class) abstract class AbstractWeld { private static final SettingsManager settingsManager = Mockito.mock(SettingsManager.class); @Inject protected ServiceFoo serviceFoo; static { // docker start String dockerConnectionString = "str"; // create some mocks Mockito.when(settingsManager.getConnectionString()).thenReturn(dockerConnectionString); } @WeldSetup public WeldInitiator weldInitiator = WeldInitiator.from(ServiceFoo.class) .activate(RequestScoped.class) .addBeans(MockBean.builder() .types(SettingsManager.class) .creating(settingsManager) .build()) .build(); @AfterEach void afterEach() { System.out.println("truncate docker database"); } } class ITTestCase1 extends AbstractWeld { @Test void doTest1() { serviceFoo.run(); } @Test void doTest2() { serviceFoo.run(); } } class ITTestCase2 extends AbstractWeld { @Test void doTest1() { serviceFoo.run(); } @Test void doTest2() { serviceFoo.run(); } } ```

manovotn commented 10 months ago

Basically idea was to create abstract test class with all setup: weld, mocks, dockers. Then extend simple test classes with only test logic (integration tests).

That should work fine, Weld-junit will look for a singular @WeldSetupconfiguration in the class hierarchy and use that. https://github.com/weld/weld-testing/tree/master/junit5#inheritance-of-test-classes

My trouble is that WeldInitiator was not static field.

It's perfectly fine to have WeldInitiator in a static field.

And maybe it is better to do Weld not static and use PER_METHOD to reinitialize request scoped beans from test to test...

That really depends on the contents of your test and the complexity to start them. Weld-junit is designed to start a minimal container so on its own it's taking very little time to boot and shouldn't cause you trouble.