Skip to content

[Spring] Scope 'cucumber-glue' is not allowed for standard (not glue) Spring Bean during context loading #1970

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

Closed
kochetkov-ma opened this issue May 9, 2020 · 8 comments
Labels
🐛 bug Defect / Bug

Comments

@kochetkov-ma
Copy link

kochetkov-ma commented May 9, 2020

Environmet: io.cucumber:cucumber-spring:5.7.0

Actual: In my Spring Configuration I use Scope 'cucumber-glue' before its registration by TestContextAdaptor#registerGlueCodeScope(org.springframework.context.ConfigurableApplicationContext).
This scope registers after Spring Context loading on glue loading stage.
But I want use this Scope for my Spring-beans because this scope is allowed for me in CucumberTestContext
And I cannot (in right way) register it manually because GlueCodeScope class has package-default visibility.

Expected: Register this scope erlier

Caused by: java.lang.IllegalStateException: No Scope registered for scope name 'cucumber-glue'

@kochetkov-ma kochetkov-ma added the 🐛 bug Defect / Bug label May 9, 2020
@mpkorstanje
Copy link
Contributor

Could you explain how to reproduce this problem and what you are trying to achieve?

But I want use this Scope for my Spring-beans because this scope is allowed for me in CucumberTestContext

This may be a documentation issue. I'm not aware of any use-case where you might want to access a glue scoped bean outside the context of a running scenario.

@kochetkov-ma
Copy link
Author

kochetkov-ma commented May 9, 2020

Could you explain how to reproduce this problem and what you are trying to achieve?

Sorry I should did it in advance. I think few people can face it, but

import io.cucumber.java.en.Given;
import io.cucumber.spring.SpringFactory;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.test.context.ContextConfiguration;

public class Issue1970 {

    @Test
    public void issue1970() {
        var factory = new SpringFactory();
        factory.addClass(GlueClass.class); // Add glue with Spring configuration
        factory.start();
    }

    @ContextConfiguration(classes = MySpringConfiguration.class)
    public static class GlueClass {

        @Given("step")
        public void step() {
            /* do nothing */
        }
    }

    public static class MySpringConfiguration {

        /**
         * It need {@link #simpleGlueScopeBean}.
         * By default Spring try to create singleton bean in an eager manner on start up.
         * There are no "cucumber-glue" scope at this moment.
         */
        @Bean
        // @Lazy // Lazy is a workaround. But every bean in init chain must be lazy.
        public String singletonBean(@Autowired String simpleGlueScopeBean) {
            return simpleGlueScopeBean + " singleton";
        }

        /**
         * Bean with 'cucumber-glue'. Not glue. 
         * I want use it in multi-thread scenario execution and Spring will manage it via scope.
         */
        @Bean
        @Scope("cucumber-glue") // This is Cucumber Glue scope
        public String simpleGlueScopeBean() {
            return "cucumber-glue";
        }
    }
}
Failed to load ApplicationContext
java.lang.IllegalStateException: Failed to load ApplicationContext
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:123)
	at io.cucumber.spring.TestContextAdaptor.createTestContextManagerAdaptor(TestContextAdaptor.java:32)
	at io.cucumber.spring.SpringFactory.start(SpringFactory.java:167)
	at ru.iopump.qa.issue.Issue1970.issue1970(Issue1970.java:17)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
	at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
	at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
	at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy5.processTestClass(Unknown Source)
	at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:118)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
	at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
	at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:412)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'singletonBean' defined in ru.iopump.qa.issue.Issue1970$MySpringConfiguration: Unexpected exception during bean creation; nested exception is java.lang.IllegalStateException: No Scope registered for scope name 'cucumber-glue'
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:530)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:128)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:275)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:243)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
	... 54 more
Caused by: java.lang.IllegalStateException: No Scope registered for scope name 'cucumber-glue'
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:353)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1287)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1207)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:885)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:789)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:539)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
	... 67 more

@mpkorstanje
Copy link
Contributor

Makes sense. Cheers.

If I understand this correctly, you're trying to stub out some behaviour of your application?

@mpkorstanje
Copy link
Contributor

I want use it in multi-thread scenario execution and Spring will manage it via scope.
Lazy is a workaround. But every bean in init chain must be lazy.

Neither of these things work the way you think it does. Scoped beans require a proxy, this proxy will initalize the been as needed when invoked in a particular scope. For this to work however you have to set the proxyMode.

@Scope(value = SCOPE_CUCUMBER_GLUE, proxyMode = ScopedProxyMode.TARGET_CLASS)

@kochetkov-ma
Copy link
Author

I know about proxies, I just missed this point in the example. I created it to demonstrate the specific situation with the scope registration, but it does not matter, because the error is reproduced with a proxy.
The essence of the problem is that the registration of the scope "cucumber-glue" in the class io.cucumber.spring.TestContextAdaptor calls after initialization of my Spring Context. If in the context there is a singleton using a bean with scope "cucumber-glue", then the application crashes because the scope has not yet been registered. And I can’t register scope myself, because GlueCodeScope.class is not public.

@mpkorstanje
Copy link
Contributor

mpkorstanje commented May 14, 2020

The cucumber-glue scope is not active when a scenario is not executed. If you do not use a proxy you'll instantiate a scenario scenario scoped bean, but this bean will be reused between scenarios. This shouldn't happen.

For example:

class Issue1970 {

    @Test
    public void issue1970() {
        ObjectFactory factory = new SpringFactory();
        factory.addClass(GlueClass.class); // Add glue with Spring configuration
        factory.start();
        GlueClass instance = factory.getInstance(GlueClass.class);
        String response = instance.service.get();
        factory.stop();
        factory.start();
        GlueClass instance2 = factory.getInstance(GlueClass.class);
        String response2 = instance2.service.get();
        factory.stop();

        assertNotEquals(response, response2);
    }

    @CucumberContextConfiguration
    @ContextConfiguration(classes = TestApplicationConfiguration.class)
    public static class GlueClass {

        @Autowired
        ExampleService service;

    }

    @Configuration
    public static class TestApplicationConfiguration {

        @Bean
        public BeanFactoryPostProcessor beanFactoryPostProcessor(){
            return factory -> factory.registerScope(SCOPE_CUCUMBER_GLUE, new GlueCodeScope());
        }

        @Bean
        public ExampleService service(ScenarioScopedApi api) {
            return new ExampleService(api);
        }

        @Bean
        @Scope(value = SCOPE_CUCUMBER_GLUE)
        public ScenarioScopedApi api() {
            return new ScenarioScopedApi();
        }

    }

    public static class ExampleService {

        final ScenarioScopedApi api;

        public ExampleService(ScenarioScopedApi api) {
            this.api = api;
        }

        String get(){
            return "Api response: " + api.get();
        }
    }

    public static class ScenarioScopedApi {

        private static final AtomicInteger globalCounter = new AtomicInteger(0);
        private final int instanceId = globalCounter.getAndIncrement();

        public String get() {
            return "instance " + instanceId;
        }

    }

}

Will fail with: java.lang.AssertionError: Values should be different. Actual: Api response: instance 0.

This happens because the GlueCodeContext currently allows beans to be created before any scenario has started. If we fix the GlueCodeContext to require an active scenario then the spring application fails to launch with:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'api': Scope 'cucumber-glue' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: cucumber-glue is not active

So if we follow these instructions and use the ScopedProxyMode.TARGET_CLASS then the bean is lazily initialized and the cucumber-glue scope doesn't need to be registered at start up. So with the configuration below we can pass the test.

   @Configuration
    public static class TestApplicationConfiguration {

        @Bean
        public ExampleService service(ScenarioScopedApi api) {
            return new ExampleService(api);
        }

        @Bean
        @Scope(value = SCOPE_CUCUMBER_GLUE, proxyMode = ScopedProxyMode.TARGET_CLASS)
        public ScenarioScopedApi api() {
            return new ScenarioScopedApi();
        }

    }

So I don't see why you'd need have to have access to the GlueCodeScope.

mpkorstanje added a commit that referenced this issue May 14, 2020
The glue code context does not require an active scenario. As a result
it was possible to create scenario scoped beans that would be shared
(leak) between scenarios.

An example of this is illustrated below. Because this does require
access to the `GlueCodeScope` it is not possible to do this in
practice.

```java
@configuration
public static class TestApplicationConfiguration {
    @bean
    public BeanFactoryPostProcessor beanFactoryPostProcessor(){
        return factory -> factory.registerScope(SCOPE_CUCUMBER_GLUE, new GlueCodeScope());
    }

    @bean
    public ExampleService service(ScenarioScopedApi api) {
        return new ExampleService(api);
    }

    @bean
    @scope(value = SCOPE_CUCUMBER_GLUE)
    public ScenarioScopedApi api() {
        return new ScenarioScopedApi();
    }
}
```

However without registering the the `GlueCodeScope` at start up Spring
will complain about missing the cucumber-glue scope rather then
complain about scope not having started or the missing proxy mode.

So to avoid further confusion:

1. GlueCodeContext will check if the context has been started.
2. Add a `@ScenarioScope` annotation that sets the correct proxy mode.
3. Do some renaming of internal classes
4. Use a global counter for conversation ids

Related
 - #1970
 - #1667
mpkorstanje added a commit that referenced this issue May 14, 2020
The glue code context does not require an active scenario. As a result
it was possible to create scenario scoped beans that would be shared
(leak) between scenarios.

An example of this is illustrated below. Because this does require
access to the `GlueCodeScope` it is not possible to do this in
practice.

```java
@configuration
public static class TestApplicationConfiguration {
    @bean
    public BeanFactoryPostProcessor beanFactoryPostProcessor(){
        return factory -> factory.registerScope(SCOPE_CUCUMBER_GLUE, new GlueCodeScope());
    }

    @bean
    public ExampleService service(ScenarioScopedApi api) {
        return new ExampleService(api);
    }

    @bean
    @scope(value = SCOPE_CUCUMBER_GLUE)
    public ScenarioScopedApi api() {
        return new ScenarioScopedApi();
    }
}
```

However without registering the the `GlueCodeScope` at start up Spring
will complain about missing the cucumber-glue scope rather then
complain about scope not having started or the missing proxy mode.

So to avoid further confusion:

1. GlueCodeContext will check if the context has been started.
2. Add a `@ScenarioScope` annotation that sets the correct proxy mode.
3. Do some renaming of internal classes
4. Use a global counter for conversation ids

Related
 - #1970
 - #1667
mpkorstanje added a commit that referenced this issue May 14, 2020
The glue code context does not require an active scenario. As a result
it was possible to create scenario scoped beans that would be shared
(leak) between scenarios.

An example of this is illustrated below. Because this does require
access to the `GlueCodeScope` it is not possible to do this in
practice.

```java
@configuration
public static class TestApplicationConfiguration {
    @bean
    public BeanFactoryPostProcessor beanFactoryPostProcessor(){
        return factory -> factory.registerScope(SCOPE_CUCUMBER_GLUE, new GlueCodeScope());
    }

    @bean
    public ExampleService service(ScenarioScopedApi api) {
        return new ExampleService(api);
    }

    @bean
    @scope(value = SCOPE_CUCUMBER_GLUE)
    public ScenarioScopedApi api() {
        return new ScenarioScopedApi();
    }
}
```

However without registering the the `GlueCodeScope` at start up Spring
will complain about missing the cucumber-glue scope rather then
complain about scope not having started or the missing proxy mode.

So to avoid further confusion:

1. GlueCodeContext will check if the context has been started.
2. Add a `@ScenarioScope` annotation that sets the correct proxy mode.
3. Do some renaming of internal classes
4. Use a global counter for conversation ids

Related
 - #1970
 - #1667
mpkorstanje added a commit that referenced this issue May 14, 2020
The glue code context does not require an active scenario. As a result
it was possible to create scenario scoped beans that would be shared
(leak) between scenarios.

An example of this is illustrated below. Because this does require
access to the `GlueCodeScope` it is not possible to do this in
practice.

```java
@configuration
public static class TestApplicationConfiguration {
    @bean
    public BeanFactoryPostProcessor beanFactoryPostProcessor(){
        return factory -> factory.registerScope(SCOPE_CUCUMBER_GLUE, new GlueCodeScope());
    }

    @bean
    public ExampleService service(ScenarioScopedApi api) {
        return new ExampleService(api);
    }

    @bean
    @scope(value = SCOPE_CUCUMBER_GLUE)
    public ScenarioScopedApi api() {
        return new ScenarioScopedApi();
    }
}
```

However without registering the the `GlueCodeScope` at start up Spring
will complain about missing the cucumber-glue scope rather then
complain about scope not having started or the missing proxy mode.

So to avoid further confusion:

1. GlueCodeContext will check if the context has been started.
2. Add a `@ScenarioScope` annotation that sets the correct proxy mode.
3. Do some renaming of internal classes
4. Use a global counter for conversation ids

Related
 - #1970
 - #1667
mpkorstanje added a commit that referenced this issue May 14, 2020
The glue code context does not require an active scenario. As a result
it was possible to create scenario scoped beans that would be shared
(leak) between scenarios.

An example of this is illustrated below. Because this does require
access to the `GlueCodeScope` it is not possible to do this in
practice.

```java
@configuration
public static class TestApplicationConfiguration {
    @bean
    public BeanFactoryPostProcessor beanFactoryPostProcessor(){
        return factory -> factory.registerScope(SCOPE_CUCUMBER_GLUE, new GlueCodeScope());
    }

    @bean
    public ExampleService service(ScenarioScopedApi api) {
        return new ExampleService(api);
    }

    @bean
    @scope(value = SCOPE_CUCUMBER_GLUE)
    public ScenarioScopedApi api() {
        return new ScenarioScopedApi();
    }
}
```

However without registering the the `GlueCodeScope` at start up Spring
will complain about missing the cucumber-glue scope rather then
complain about scope not having started or the missing proxy mode.

So to avoid further confusion:

1. GlueCodeContext will check if the context has been started.
2. Add a `@ScenarioScope` annotation that sets the correct proxy mode.
3. Do some renaming of internal classes
4. Use a global counter for conversation ids

Related
 - #1970
 - #1667
mpkorstanje added a commit that referenced this issue May 14, 2020
The glue code context does not require an active scenario. As a result
it was possible to create scenario scoped beans that would be shared
(leak) between scenarios.

An example of this is illustrated below. Because this does require
access to the `GlueCodeScope` it is not possible to do this in
practice.

```java
@configuration
public static class TestApplicationConfiguration {
    @bean
    public BeanFactoryPostProcessor beanFactoryPostProcessor(){
        return factory -> factory.registerScope(SCOPE_CUCUMBER_GLUE, new GlueCodeScope());
    }

    @bean
    public ExampleService service(ScenarioScopedApi api) {
        return new ExampleService(api);
    }

    @bean
    @scope(value = SCOPE_CUCUMBER_GLUE)
    public ScenarioScopedApi api() {
        return new ScenarioScopedApi();
    }
}
```

However without registering the the `GlueCodeScope` at start up Spring
will complain about missing the cucumber-glue scope rather then
complain about scope not having started or the missing proxy mode.

So to avoid further confusion:

1. GlueCodeContext will check if the context has been started.
2. Add a `@ScenarioScope` annotation that sets the correct proxy mode.
3. Do some renaming of internal classes
4. Use a global counter for conversation ids

Related
 - #1970
 - #1667
@kochetkov-ma
Copy link
Author

kochetkov-ma commented May 14, 2020

I use glue scope to save scenario vars

@Slf4j
@Component
@Scope(value = PumpConstants.SCENARIO_SCOPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ScenarioVars extends AbstractVars {
    @Value("${pump.bind.scenario:#{null}}")
    private String overriddenBind;

    @Override
    public String bindName() {
        return overriddenBind == null ? "scenario" : overriddenBind;
    }
}

Then inject it to hook

public class CoreCucumberHook {

    private final ApplicationEventPublisher eventPublisher;
    private final Collection<String> directBindings;
    private final ScenarioVars scenarioVars;
    private final FeatureVars featureVars;

    public CoreCucumberHook(ApplicationEventPublisher eventPublisher,
                            Collection<String> directBindings,
                            ScenarioVars scenarioVars,
                            FeatureVars featureVars) {
        this.eventPublisher = eventPublisher;
        this.directBindings = directBindings;
        this.scenarioVars = scenarioVars;
        this.featureVars = featureVars;
        Execution.checkRunner();
    }

    @Before(value = "@StepVars", order = 0)
    public void extractStepVars() {
        directBindings.add(scenarioVars.bindName());
    }

And private final Collection<String> directBindings has glue scope too
I have changed my code and now everything is ok, because my hooks with my beans are created after test execution start.
May be it not good to call methods while creationing a bean, but if you add this line, it will throw an exception java.lang.IllegalStateException: No Scope registered for scope name 'cucumber-glue'

   @Configuration
    public static class TestApplicationConfiguration {

        @Bean
        public ExampleService service(ScenarioScopedApi api) {
            api.get(); // call some method here
            return new ExampleService(api);
        }

        @Bean
        @Scope(value = SCOPE_CUCUMBER_GLUE, proxyMode = ScopedProxyMode.TARGET_CLASS)
        public ScenarioScopedApi api() {
            return new ScenarioScopedApi();
        }

    }

@mpkorstanje
Copy link
Contributor

May be it not good to call methods while creationing a bean, but if you add this line, it will throw an exception java.lang.IllegalStateException: No Scope registered for scope name 'cucumber-glue'

Can you try this with #1974. It should correctly complain about 'cucumber-glue' scope not being active.

mpkorstanje added a commit that referenced this issue May 14, 2020
The glue code context does not require an active scenario. As a result
it was possible to create scenario scoped beans that would be shared
(leak) between scenarios.

An example of this is illustrated below. Because this does require
access to the `GlueCodeScope` it is not possible to do this in
practice.

```java
@configuration
public static class TestApplicationConfiguration {
    @bean
    public BeanFactoryPostProcessor beanFactoryPostProcessor(){
        return factory -> factory.registerScope(SCOPE_CUCUMBER_GLUE, new GlueCodeScope());
    }

    @bean
    public ExampleService service(ScenarioScopedApi api) {
        return new ExampleService(api);
    }

    @bean
    @scope(value = SCOPE_CUCUMBER_GLUE)
    public ScenarioScopedApi api() {
        return new ScenarioScopedApi();
    }
}
```

However without registering the the `GlueCodeScope` at start up Spring
will complain about missing the cucumber-glue scope rather then
complain about scope not having started or the missing proxy mode.

So to avoid further confusion:

1. GlueCodeContext will check if the context has been started.
2. Add a `@ScenarioScope` annotation that sets the correct proxy mode.
3. Do some renaming of internal classes
4. Use a global counter for conversation ids

Related
 - #1970
 - #1667
mpkorstanje added a commit that referenced this issue May 14, 2020
The glue code context does not require an active scenario. As a result
it was possible to create scenario scoped beans that would be shared
(leak) between scenarios.

An example of this is illustrated below. Because this does require
access to the `GlueCodeScope` it is not possible to do this in
practice.

```java
@configuration
public static class TestApplicationConfiguration {
    @bean
    public BeanFactoryPostProcessor beanFactoryPostProcessor(){
        return factory -> factory.registerScope(SCOPE_CUCUMBER_GLUE, new GlueCodeScope());
    }

    @bean
    public ExampleService service(ScenarioScopedApi api) {
        return new ExampleService(api);
    }

    @bean
    @scope(value = SCOPE_CUCUMBER_GLUE)
    public ScenarioScopedApi api() {
        return new ScenarioScopedApi();
    }
}
```

However without registering the the `GlueCodeScope` at start up Spring
will complain about missing the cucumber-glue scope rather then
complain about scope not having started or the missing proxy mode.

So to avoid further confusion:

1. GlueCodeContext will check if the context has been started.
2. Add a `@ScenarioScope` annotation that sets the correct proxy mode.
3. Do some renaming of internal classes
4. Use a global counter for conversation ids

Related
 - #1970
 - #1667
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 bug Defect / Bug
Projects
None yet
Development

No branches or pull requests

2 participants