package org.jbehave.core.steps.spring; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.jbehave.core.annotations.Given; import org.jbehave.core.configuration.MostUsefulConfiguration; import org.jbehave.core.steps.CandidateSteps; import org.jbehave.core.steps.Steps; import org.jbehave.core.steps.spring.SpringStepsFactoryBehaviour.FooSteps; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; public class SpringStepsFactoryAOPBehaviour { @Test public void aopEnvelopedStepsCanBeCreated() { // Given ApplicationContext context = createApplicationContext(StepsWithAOPAnnotationConfiguration.class .getName()); SpringStepsFactory factory = new SpringStepsFactory( new MostUsefulConfiguration(), context); // When List<CandidateSteps> steps = factory.createCandidateSteps(); // Then assertAOPFooStepsFound(steps); } private void assertAOPFooStepsFound(List<CandidateSteps> steps) { // // Only one returned, the IFooSteps will not be detected assertEquals(1, steps.size()); assertThat(steps, hasItem(isCandidateStepInstanceOf(FooSteps.class))); // Make it explicit that the steps bean with the annotation in the // interface is not provided assertThat(steps, not(hasItem(isCandidateStepInstanceOf(IFooSteps.class)))); } private ApplicationContext createApplicationContext(String... resources) { return new SpringApplicationContextFactory(resources) .createApplicationContext(); } @Configuration @EnableAspectJAutoProxy public static class StepsWithAOPAnnotationConfiguration { @Bean public FooAspect fooAspect() { return new FooAspect(); } // JDK Proxy @Bean @Scope(proxyMode = ScopedProxyMode.INTERFACES) public SpringStepsFactoryAOPBehaviour.IFooSteps iFooSteps() { return new SpringStepsFactoryAOPBehaviour.FooStepsImpl(); } // CGLIB-based @Bean @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) public SpringStepsFactoryBehaviour.FooSteps fooSteps() { return new SpringStepsFactoryBehaviour.FooSteps(); } } @Aspect public static class FooAspect { public final AtomicInteger executions = new AtomicInteger(0); @Around(value = "bean(fooSteps) || bean(iFooSteps)") public Object around(ProceedingJoinPoint pjp) throws Throwable { try { return pjp.proceed(); } finally { System.err.println("Accounted for " + executions.incrementAndGet() + " executions"); } } } public static interface IFooSteps { @Given("a step declared in an interface, with a $param") void aStepWithAParam(String param); } public static class FooStepsImpl implements IFooSteps { public void aStepWithAParam(String param) { } } public static CandidateStepsInstanceOfMatcher isCandidateStepInstanceOf( Class<?> target) { return new CandidateStepsInstanceOfMatcher(target); } private static class CandidateStepsInstanceOfMatcher extends BaseMatcher<CandidateSteps> { private final Class<?> target; public CandidateStepsInstanceOfMatcher(Class<?> target) { this.target = target; } public boolean matches(Object item) { if (item instanceof CandidateSteps) { Object instance = ((Steps) item).instance(); boolean result = target.isAssignableFrom(instance.getClass()); return result; } return false; } public void describeTo(Description description) { description .appendText("Step class instantiated from this CandidateStep is of type " + target.getName()); } } }