Skip to content

Commit

Permalink
cucumber#2491 Support @CucumberContextConfiguration as a meta-annota…
Browse files Browse the repository at this point in the history
…tion

 Using @CucumberContextConfiguration as a meta-annotation caused a CucumberBackendException because SpringFactory only realized raw use of the class. Method hasCucumberContextConfiguration does now recognize the use of @CucumberContextConfiguration as meta-annotation or with inheritance.

 Also SpringBackend#loadGlue filters out abstract classes and interfaces to not try to instantiate what cannot be instantiated.
  • Loading branch information
michael committed Oct 23, 2022
1 parent 922c1c5 commit c63783b
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.cucumber.core.resource.ClasspathScanner;
import io.cucumber.core.resource.ClasspathSupport;

import java.lang.reflect.Modifier;
import java.net.URI;
import java.util.Collection;
import java.util.List;
Expand All @@ -31,7 +32,8 @@ public void loadGlue(Glue glue, List<URI> gluePaths) {
.map(ClasspathSupport::packageName)
.map(classFinder::scanForClassesInPackage)
.flatMap(Collection::stream)
.filter((Class<?> clazz) -> clazz.getAnnotation(CucumberContextConfiguration.class) != null)
.filter(SpringFactory::hasCucumberContextConfiguration)
.filter(this::checkIfOfClassTypeAndNotAbstract)
.distinct()
.forEach(container::addClass);
}
Expand All @@ -51,4 +53,7 @@ public Snippet getSnippet() {
return null;
}

private boolean checkIfOfClassTypeAndNotAbstract(Class<?> clazz) {
return !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.cucumber.core.resource.ClasspathSupport;
import org.apiguardian.api.API;
import org.springframework.beans.BeansException;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.BootstrapWith;
Expand Down Expand Up @@ -82,8 +83,10 @@ private static void checkNoComponentAnnotations(Class<?> type) {
}
}

private static boolean hasCucumberContextConfiguration(Class<?> stepClass) {
return stepClass.getAnnotation(CucumberContextConfiguration.class) != null;
public static boolean hasCucumberContextConfiguration(Class<?> stepClass) {
return stepClass.getAnnotation(CucumberContextConfiguration.class) != null
|| AnnotatedElementUtils.hasMetaAnnotationTypes(stepClass, CucumberContextConfiguration.class)
|| AnnotatedElementUtils.getMergedAnnotation(stepClass, CucumberContextConfiguration.class) != null;
}

private void checkOnlyOneClassHasCucumberContextConfiguration(Class<?> stepClass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

import io.cucumber.core.backend.Glue;
import io.cucumber.core.backend.ObjectFactory;
import io.cucumber.core.backend.StepDefinition;
import io.cucumber.spring.annotationconfig.AnnotationContextConfiguration;
import io.cucumber.spring.cucontextconfig.AbstractWithComponentAnnotation;
import io.cucumber.spring.cucontextconfig.WithMetaAnnotation;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

Expand Down Expand Up @@ -52,4 +51,13 @@ void finds_annotaiton_context_configuration_once_by_classpath_url() {
verify(factory, times(1)).addClass(AnnotationContextConfiguration.class);
}

@Test
void ignoresAbstractClassWithCucumberContextConfiguration() {
backend.loadGlue(glue, singletonList(
URI.create("classpath:io/cucumber/spring/cucontextconfig")));
backend.buildWorld();
verify(factory, times(0)).addClass(AbstractWithComponentAnnotation.class);
verify(factory, times(1)).addClass(WithMetaAnnotation.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.cucumber.spring.componentannotation.WithControllerAnnotation;
import io.cucumber.spring.contextconfig.BellyStepDefinitions;
import io.cucumber.spring.contexthierarchyconfig.WithContextHierarchyAnnotation;
import io.cucumber.spring.cucontextconfig.WithMetaAnnotation;
import io.cucumber.spring.dirtiescontextconfig.DirtiesContextBellyStepDefinitions;
import io.cucumber.spring.metaconfig.dirties.DirtiesContextBellyMetaStepDefinitions;
import io.cucumber.spring.metaconfig.general.BellyMetaStepDefinitions;
Expand Down Expand Up @@ -359,6 +360,17 @@ void shouldBeStoppableWhenFacedWithFailedApplicationContext(Class<?> contextConf
assertDoesNotThrow(factory::stop);
}

@ParameterizedTest
@ValueSource(classes = {
WithMetaAnnotation.class,
})
void shouldNotFailWithCucumberContextConfigurationMetaAnnotation(Class<?> contextConfiguration) {
final ObjectFactory factory = new SpringFactory();
factory.addClass(contextConfiguration);

assertDoesNotThrow(factory::start);
}

@CucumberContextConfiguration
@ContextConfiguration("classpath:cucumber.xml")
public static class WithSpringAnnotations {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.cucumber.spring.cucontextconfig;

import io.cucumber.spring.CucumberContextConfiguration;
import org.springframework.test.context.ContextConfiguration;

@CucumberContextConfiguration
@ContextConfiguration("classpath:cucumber.xml")
public abstract class AbstractWithComponentAnnotation {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.cucumber.spring.cucontextconfig;

import io.cucumber.spring.CucumberContextConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;

import java.lang.annotation.*;

@MyTestAnnotation
public class WithMetaAnnotation {
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited

@CucumberContextConfiguration // Required for Cucumber + Spring
@ComponentScan("some.test.stuff")
@ContextConfiguration(classes = SomeGeneralPurposeTestContext.class)
@TestPropertySource(properties = "testprop=value_for_testing")
@ActiveProfiles("test")
@interface MyTestAnnotation {

}

class SomeGeneralPurposeTestContext {
}

0 comments on commit c63783b

Please sign in to comment.