Skip to content
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

Spring transactions still not working #717

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/spring-txn/src/main/resources/jdbc.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
database.driver=org.hsqldb.jdbcDriver
database.url=jdbc:hsqldb:mem:user
database.url=jdbc:hsqldb:file:target/db
database.user=sa
database.password=

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
@CucumberOptions(glue = {"cucumber.examples.spring.txn", "cucumber.runtime.java.spring.hooks"})
@CucumberOptions(glue = {"cucumber.examples.spring.txn", "cucumber.api.spring"})
public class RunCukesTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cucumber.examples.spring.txn;

import cucumber.api.java.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;

import static org.junit.Assert.assertEquals;

@WebAppConfiguration
@ContextConfiguration("classpath:cucumber.xml")
public class TransactionCheckHooks {
@Autowired
private UserRepository userRepository;

@Autowired
private MessageRepository messageRepository;

@Before
public void verifyEmptyDatabase() {
assertEquals(0, userRepository.count());
assertEquals(0, messageRepository.count());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import static org.junit.Assert.assertTrue;

import java.util.List;

Expand Down Expand Up @@ -31,5 +34,7 @@ public void a_User_has_posted_the_following_messages(List<Message> messages) thr
m.setAuthor(user);
messageRepository.save(m);
}
assertTrue("No transaction is active",
TransactionSynchronizationManager.isActualTransactionActive());
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
@txn
Feature: See Messages

Scenario: See another user's messages
Expand Down
212 changes: 112 additions & 100 deletions spring/src/main/java/cucumber/runtime/java/spring/SpringFactory.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
package cucumber.runtime.java.spring;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import cucumber.runtime.CucumberException;
import cucumber.runtime.java.ObjectFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.TestContextManager;

import cucumber.runtime.CucumberException;
import cucumber.runtime.java.ObjectFactory;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashSet;

/**
* Spring based implementation of ObjectFactory.
* <p/>
* <p>
* <ul>
* <li>It uses TestContextManager to create and prepare test instances. Configuration via: @ContextConfiguration
* <li>It uses TestContextManager to create and prepare test instances.
* Configuration via: @ContextConfiguration of @ContextHierarcy
* At least on step definition class needs to have a @ContextConfiguration or
* @ContextHierarchy annotation. If more that one step definition class has such
* an annotation, the annotations must be equal on the different step definition
* classes.</li>
* <li>The step definitions class with @ContextConfiguration or @ContextHierarchy
* annotation, may also have a @WebAppConfiguration or @DirtiesContext annotation.
* </li>
* <li>It also uses a context which contains the step definitions and is reloaded for each
* scenario.</li>
* <li>The step definitions added to the TestContextManagers context and
* is reloaded for each scenario.</li>
* </ul>
* </p>
* <p/>
Expand All @@ -39,137 +42,146 @@
*/
public class SpringFactory implements ObjectFactory {

private static ConfigurableApplicationContext applicationContext;
private static ConfigurableListableBeanFactory beanFactory;
private ConfigurableListableBeanFactory beanFactory;
private CucumberTestContextManager testContextManager;

private final Collection<Class<?>> stepClasses = new HashSet<Class<?>>();
private final Map<Class<?>, TestContextManager> contextManagersByClass = new HashMap<Class<?>, TestContextManager>();
private Class<?> stepClassWithSpringContext = null;

public SpringFactory() {
}

static {
applicationContext = new GenericXmlApplicationContext("cucumber/runtime/java/spring/cucumber-glue.xml");
applicationContext.registerShutdownHook();
beanFactory = applicationContext.getBeanFactory();
}

@Override
public void addClass(final Class<?> stepClass) {
if (!stepClasses.contains(stepClass)) {
if (dependsOnSpringContext(stepClass)) {
if (stepClassWithSpringContext == null) {
stepClassWithSpringContext = stepClass;
} else {
checkAnnotationsEqual(stepClassWithSpringContext, stepClass);
}
}
stepClasses.add(stepClass);

BeanDefinitionRegistry registry = (BeanDefinitionRegistry) applicationContext.getAutowireCapableBeanFactory();
BeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(stepClass)
.setScope(GlueCodeScope.NAME)
.getBeanDefinition();
registry.registerBeanDefinition(stepClass.getName(), beanDefinition);
}
}

@Override
public void start() {
GlueCodeContext.INSTANCE.start();
}

@Override
public void stop() {
notifyContextManagersAboutTestClassFinished();

GlueCodeContext.INSTANCE.stop();
beanFactory.destroySingletons();
private void checkAnnotationsEqual(Class<?> stepClassWithSpringContext, Class<?> stepClass) {
Annotation[] annotations1 = stepClassWithSpringContext.getAnnotations();
Annotation[] annotations2 = stepClass.getAnnotations();
if (annotations1.length != annotations2.length) {
throw new CucumberException("Annotations differs on glue classes found: " +
stepClassWithSpringContext.getName() + ", " +
stepClass.getName());
}
for (Annotation annotation : annotations1) {
if (!isAnnotationInArray(annotation, annotations2)) {
throw new CucumberException("Annotations differs on glue classes found: " +
stepClassWithSpringContext.getName() + ", " +
stepClass.getName());
}
}
}

private void notifyContextManagersAboutTestClassFinished() {
Map<Class<?>, Exception> exceptionsThrown = new HashMap<Class<?>, Exception>();

for (Map.Entry<Class<?>, TestContextManager> classTestContextManagerEntry : contextManagersByClass
.entrySet()) {
try {
classTestContextManagerEntry.getValue().afterTestClass();
} catch (Exception e) {
exceptionsThrown.put(classTestContextManagerEntry.getKey(), e);
private boolean isAnnotationInArray(Annotation annotation, Annotation[] annotations) {
for (Annotation annotationFromArray: annotations) {
if (annotation.equals(annotationFromArray)) {
return true;
}
}

contextManagersByClass.clear();

rethrowExceptionsIfAny(exceptionsThrown);
return false;
}

private void rethrowExceptionsIfAny(Map<Class<?>, Exception> exceptionsThrown) {
if (exceptionsThrown.isEmpty()) {
return;
@Override
public void start() {
if (stepClassWithSpringContext == null) {
throw new CucumberException("No glue class with @ContextConfiguration or " +
"@ContextHierarcy annotation found in: " + stepClasses.toString());
}
testContextManager = new CucumberTestContextManager(stepClassWithSpringContext);
notifyContextManagerAboutTestClassStarted();
if (isFirstScenario() || isNewContextCreated()) {
beanFactory = testContextManager.getBeanFactory();
for (Class<?> stepClass : stepClasses) {
registerStepClassBeanDefinition(stepClass);
}
}
GlueCodeContext.INSTANCE.start();
}

if (exceptionsThrown.size() == 1) {
//ony one exception, throw an exception with the correct cause
Exception e = exceptionsThrown.values().iterator().next();
private void notifyContextManagerAboutTestClassStarted() {
try {
testContextManager.beforeTestClass();
} catch (Exception e) {
throw new CucumberException(e.getMessage(), e);
}
}

//multiple exceptions but we can only have one cause, put relevant info in the exception message
//to not lose the interesting data
throw new CucumberException(getMultipleExceptionMessage(exceptionsThrown));
private boolean isFirstScenario() {
return beanFactory == null;
}

private String getMultipleExceptionMessage(Map<Class<?>, Exception> exceptionsThrow) {
StringBuilder exceptionsThrown = new StringBuilder(1000);
exceptionsThrown.append("Multiple exceptions occurred during processing of the TestExecutionListeners\n\n");
private boolean isNewContextCreated() {
return !beanFactory.equals(testContextManager.getBeanFactory());
}

for (Map.Entry<Class<?>, Exception> classExceptionEntry : exceptionsThrow.entrySet()) {
exceptionsThrown.append("Exception during processing of TestExecutionListeners of ");
exceptionsThrown.append(classExceptionEntry.getKey());
exceptionsThrown.append('\n');
exceptionsThrown.append(classExceptionEntry.getValue().toString());
exceptionsThrown.append('\n');
private void registerStepClassBeanDefinition(Class<?> stepClass) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
BeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(stepClass)
.setScope(GlueCodeScope.NAME)
.getBeanDefinition();
registry.registerBeanDefinition(stepClass.getName(), beanDefinition);
}

StringWriter stackTraceStringWriter = new StringWriter();
PrintWriter stackTracePrintWriter = new PrintWriter(stackTraceStringWriter);
classExceptionEntry.getValue().printStackTrace(stackTracePrintWriter);
exceptionsThrown.append(stackTraceStringWriter.toString());
exceptionsThrown.append('\n');
@Override
public void stop() {
notifyContextManagerAboutTestClassFinished();
GlueCodeContext.INSTANCE.stop();
}

private void notifyContextManagerAboutTestClassFinished() {
try {
testContextManager.afterTestClass();
} catch (Exception e) {
throw new CucumberException(e.getMessage(), e);
}

return exceptionsThrown.toString();
}

@Override
public <T> T getInstance(final Class<T> type) {
if (!beanFactory.containsSingleton(type.getName())) {
beanFactory.registerSingleton(type.getName(), getTestInstance(type));
try {
return beanFactory.getBean(type);
} catch (BeansException e) {
throw new CucumberException(e.getMessage(), e);
}

return applicationContext.getBean(type);
}

private <T> T getTestInstance(final Class<T> type) {
try {
T instance = createTest(type);
private boolean dependsOnSpringContext(Class<?> type) {
return type.isAnnotationPresent(ContextConfiguration.class)
|| type.isAnnotationPresent(ContextHierarchy.class);
}
}

if (dependsOnSpringContext(type)) {
TestContextManager contextManager = new TestContextManager(type);
contextManager.prepareTestInstance(instance);
contextManager.beforeTestClass();
class CucumberTestContextManager extends TestContextManager {

contextManagersByClass.put(type, contextManager);
}
public CucumberTestContextManager(Class<?> testClass) {
super(testClass);
registerGlueCodeScope(getContext());
}

return instance;
} catch (Exception e) {
throw new CucumberException(e.getMessage(), e);
}
public ConfigurableListableBeanFactory getBeanFactory() {
return getContext().getBeanFactory();
}

@SuppressWarnings("unchecked")
protected <T> T createTest(Class<T> type) throws Exception {
return (T) type.getConstructors()[0].newInstance();
private ConfigurableApplicationContext getContext() {
return (ConfigurableApplicationContext)getTestContext().getApplicationContext();
}

private boolean dependsOnSpringContext(Class<?> type) {
return type.isAnnotationPresent(ContextConfiguration.class)
|| type.isAnnotationPresent(ContextHierarchy.class);
private void registerGlueCodeScope(ConfigurableApplicationContext context) {
do {
context.getBeanFactory().registerScope(GlueCodeScope.NAME, new GlueCodeScope());
context = (ConfigurableApplicationContext)context.getParent();
} while (context != null);
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Loading