Coder Social home page Coder Social logo

Comments (3)

visusnet avatar visusnet commented on June 12, 2024

Well, yes, you're right. The SpringJUnit4ClassRunner uses three test execution listeners:

  • org.springframework.test.context.support.DependencyInjectionTestExecutionListener
  • org.springframework.test.context.support.DirtiesContextTestExecutionListener
  • org.springframework.test.context.transaction.TransactionalTestExecutionListener

These default test execution listeners are defined in org.springframework.test.context.TestContextManager. The SpringJUnitReportingRunner only calls prepareTestInstance() on the TextContextManager instance which delegates the call to the test execution listeners and this method is not overridden by the TransactionalTestExecutionListener. TransactionalTestExecutionListener's magic happens in beforeTestMethod() and afterTestMethod() but the SpringJUnitReportingRunner is not using them.

from jbehave-junit-runner.

tuannm-sou avatar tuannm-sou commented on June 12, 2024

@visusnet
Thank you for your quick response, very detailed explanation 😊
I know JBehave (and general BDD implementations) focus on acceptance tests, so it's often unnecessary to support transaction rollback. But in some cases, it is required to rollback transaction after each test for clean data.
By your given information, I tried to find a solution that could make SpringJUnitReportingRunner works well with Spring Transactional annotation. Lucky I found one, the following is my snippets:

  • JUnit-runnable entry-point to run multiple stories: NewContactServiceBDDTest.java

ℹ️ override run() method with addition of Spring Transactional annotation

@RunWith(SpringJUnitReportingRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring-servlet.xml")
public class NewContactServiceBDDTest extends JUnitStories {

    @Autowired
    private ApplicationContext context;


    @Override
    public Configuration configuration() {
        return new MostUsefulConfiguration();
    }

    public InjectableStepsFactory stepsFactory() {
        return new SpringStepsFactory(configuration(), context);
    }

    @Override
    protected List<String> storyPaths() {
        return Arrays.asList("net/viralpatel/contact/service/add_contact.story");
    }

    @Transactional
    public void run() throws Throwable {
        super.run();
    }
}
  • Modified runner: SpringJUnitReportingRunner.java

ℹ️ override JUnitReportingRunner.run(RunNotifier notifier) method, add new org.junit.runner.notification.RunListener to notifier. It will invoke org.springframework.test.context.TestContextManager beforeTestMethod(...) and afterTestMethod(...) to fire TransactionalTestExecutionListener execution

public class SpringJUnitReportingRunner extends JUnitReportingRunner {

    SpringJunit4ClassRunnerExecutor executor;
    ConfigurableEmbedder ce;

    public SpringJUnitReportingRunner(Class<? extends ConfigurableEmbedder> testClass) throws Throwable {
        super(testClass);
    }

    @Override
    protected void prepareConfigurableEmbedder(Class<? extends ConfigurableEmbedder> testClass, ConfigurableEmbedder configurableEmbedder) throws Exception {
        ce = configurableEmbedder;
        executor = new SpringJunit4ClassRunnerExecutor(testClass);
        executor.getHiddenTestContextManager().prepareTestInstance(configurableEmbedder);
    }

    @Override
    public void run(RunNotifier notifier) {
        notifier.addListener(new RunListener() {

            public void testRunStarted(Description description) throws Exception {
                System.err.println("testRunStarted");
                executor.getHiddenTestContextManager().beforeTestMethod(ce, description.getTestClass().getDeclaredMethod("run", new Class[] {}));
            }

            public void testRunFinished(Result result) throws Exception {
                System.err.println("testRunFinished");
                executor.getHiddenTestContextManager().afterTestMethod(ce, ce.getClass().getDeclaredMethod("run", new Class[] {}), new Throwable("error"));
            }
        });
        super.run(notifier);
    }

    private static class SpringJunit4ClassRunnerExecutor extends SpringJUnit4ClassRunner {
        public SpringJunit4ClassRunnerExecutor(Class<?> clazz) throws InitializationError {
            super(clazz);
        }

        public TestContextManager getHiddenTestContextManager() {
            return getTestContextManager();
        }
    }
}
  • Important: To support Spring transaction rollback, must change execution mechanism of org.jbehave.core.embedder.StoryManager to SEQUENCE (it means we won't use java.util.concurrent.ExecutorService to run stories concurrency, but one by one)
  • Now, everything works well, transaction rollback automatically after each test/ stories ☺️

capture_01

capture_02

The above snippets are only my draft, I know it's not a good solution, just makes JBehave, Spring and JUnit work together, Could you please investigate and make it better? I believe you can do it πŸ‘

I will follow you!

from jbehave-junit-runner.

visusnet avatar visusnet commented on June 12, 2024

Wow, that surely looks like a promising approach. However, there are some drawbacks (for example: usage of reflection, the necessary manual override of the run() method in a class that inherits from JUnitStories, instantiating Throwable). I think the additional RunListener is the important step. What's bothering me most is the fact, that you need to provide a run method that is annotated with @Transactional but then again: You need to state somewhere that you want to enable transaction support (maybe on class level?).

During further investigation, I noticed that a @Transactional annotation on class level should work. Here is why:

Unfortunately, I don't see a way around the reflection magic... Mhmm...

from jbehave-junit-runner.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.