Hot questions for Using Cucumber in spring boot

Question:

we have a spring boot application which we want to test via serenity (former Thucydides). Theoretically the tests can be run (if i test for example www.google.com everything works fine) but I want to test my own application and not google ;)

So I need to start the application before running the tests. Normally we have an annotation

@RunWith(SpringJUnit4ClassRunner.class)

at our test class. but with Serenity and cucumber we need

@RunWith(CucumberWithSerenity.class)

and it is not possible to add 2 @RunWith annotations.

What is the best way to get the tests wit Serenity and Cucumber running?


Answer:

Upgrade to Spring 4.2.1 and you should be able to use the Serenity runner:

http://docs.spring.io/spring/docs/4.2.1.RELEASE/spring-framework-reference/htmlsingle/#testcontext-junit4-rules

Question:

I'm trying to create a Springboot, cucumber and Junit4 automation framework. The versions I used as below:

  • Springboot: 2.1.3
  • cucumber: io.cucumber 4.2.3
  • Junit4: 4.12
  • Operating System: Win7 Pro.

I created a prop class which trying to get properties from property file (.yml)

Prop Class:

@Data
@Component
public class PropsConfig {
    @Value("${spring.skyewss}")
    public String url;
}

Step defs:

public class SkyeWssLoginStepDef implements En {

    private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    private WebDriver driver;
    private SkyeWssLoginPage loginPage;
    private SkyeWssUtil skyeWssUtil;
    @Autowired
    private PropsConfig propsConfig;

    public SkyeWssLoginStepDef() {


        Given("^I open Skye WSS web page$", () -> {
            driver = CukeHook.driver;
            loginPage = new SkyeWssLoginPage(driver);
            driver.get(propsConfig.getUrl());
            skyeWssUtil = new SkyeWssUtil();
            LOGGER.info("Current page is " + driver.getTitle());
        });
       }
       ......
     }

Cucumber runner class:

@RunWith(Cucumber.class)
@CucumberOptions(
        features = {"src/test/resources/features"},
        plugin = {"pretty", "html:target/cucumber-html-report"},
        tags = {"@SkyeWss"}
)
@SpringBootTest
public class WssRegApplicationTests {

}

I've tried to give tags on stepdef classes, but no luck. When I give stepdef classes tags like @Component or @SrpingBootTest, I will get error.

cucumber.runtime.CucumberException: Glue class class com.flexicards.wss_reg.skye.step.SkyeWssLoginStepDef and class com.flexicards.wss_reg.skye.step.SkyeWssDashboardValStepDef both attempt to configure the spring context. Please ensure only one glue class configures the spring context


cucumber.runtime.CucumberException: Glue class com.flexicards.wss_reg.skye.step.SkyeWssDashboardValStepDef was annotated with @Component; marking it as a candidate for auto-detection by Spring. Glue classes are detected and registered by Cucumber. Auto-detection of glue classes by spring may lead to duplicate bean definitions. Please remove the @Component annotation

I'm new to Spring and Springboot, I'm pretty sure I did not configure correctly in somewhere. Most example for springboot and cucumber out there are outdated. I've tried them already. Like create an abstract classes which extended by all the stepdefs classes. This will give me the same error as @SpringBootTest one.

Could anyone help me on this? Any inputs are welcomed. Thank you very much.


Answer:

Looks like you've done almost everything right. The only thing out of place is the the location of your context configuration. It has to be in a file with a step or hook definition. Otherwise Cucumber won't detect it. This should do the trick:

@SpringBootTest
@AutoConfigureMockMvc
public class CucumberContextConfiguration  {

    @Before
    public void setup_cucumber_spring_context(){
        // Dummy method so cucumber will recognize this class as glue
        // and use its context configuration.
    }
} 

You can find a working example of cucumber-spring in the cucumber github repository.

Perhaps also worth to keep in mind that Cucumber implements step definitions as spring beans rather then post processed unit test classes as you might expect. This means that @MockBean, @SpyBean and friends won't work.

Question:

I'm having a problem running cucumber test with Spring Boot 2.

I have two steps definitions and in both clases I try to spy an object with mockito trying to capture the argument passed to one method of this class.

The point is that since Cucumber only allows one Spring Application Context Configuration I've created an abstract class to configure it and I've extended one of the steps definition with this class.

@ActiveProfiles("INTEGRATION_TEST")
@SpringBootTest
@ContextConfiguration(classes = ServiceApplication.class)
@TestPropertySource(properties = 
      {"spring.config.location=classpath:application-test.yml"})
public abstract class BaseTestIntegration {}


@Ignore
public class OfferEventsGenerationStep extends BaseTestIntegration {

@Autowired private LoanOfferBatchController loanOfferBatchController;
@SpyBean private SendEventOfferServiceImpl sendEventService;
@Captor private ArgumentCaptor<CreateOfferToUserEvent> createOfferEventCaptor;
@Autowired private GenericWebApplicationContext context;
.........
 @Then("^events will be created$")
public void eventsWillBeCreated() throws Throwable {
    Mockito.verify(sendEventService, Mockito.times(5)).sendEvent(createOfferEventCaptor.capture());
 }
}


@Ignore
public class SecondEventsGenerationStep  {

@Autowired private LoanOfferBatchController loanOfferBatchController;
@SpyBean private SendEventSencondServiceImpl sendEventService;
@Captor private ArgumentCaptor<CreateOfferToUserEvent> createOfferEventCaptor;
@Autowired private GenericWebApplicationContext context;
.........
 @Then("^events will be created$")
public void eventsWillBeCreated() throws Throwable {
    Mockito.verify(sendEventService, Mockito.times(2)).sendEvent(createOfferEventCaptor.capture());
 }
}

And everything works fine except that sendEventService is only recognized as a spy bean in the class that extends the BaseTestIntegration class the other one throws this exception:

 org.mockito.exceptions.misusing.NotAMockException: 
 Argument passed to verify() is of type SendEventSencondServiceImpl and is not a mock!

Answer:

It's currently not possible to use @MockBean or @SpyBean because cucumber-spring turns step definitions into beans and does not use the TestContextManager. There is an issue to Support @MockBean in cucumber-spring #1470. It's up for grabs if anybody wants to take it.

Question:

I'm implementing a SchedulerService that uses a AgentRestClient bean to get some data from an external system. It looks something like this:

@Service
public class SchedulerService {

  @Inject
  private AgentRestClient agentRestClient;

  public String updateStatus(String uuid) {
    String status = agentRestClient.get(uuid);
    ...
  }
  ...
}

To test this service, I'm using Cucumber while at the same time I'm trying to mock the behavior of AgentRestClient using Spring Boot's @MockBean annotation, as follows:

import cucumber.api.CucumberOptions;
import cucumber.api.java.Before;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = CentralApp.class)
@CucumberOptions(glue = {"com.company.project.cucumber.stepdefs", "cucumber.api.spring"})
public class RefreshActiveJobsStepDefs {

  @MockBean
  private AgentRestClient agentRestClient;

  @Inject
  private SchedulerService schedulerService;

  @Before
  public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    given(agentRestClient.get(anyString())).willReturn("FINISHED");//agentRestClient is always null here
  }

  //Skipping the actual Given-When-Then Cucumber steps...
}

When I try to run any Cucumber scenario, the agentRestClient is never mocked/injected. The setUp() method fails with a NPE:

java.lang.NullPointerException
  at com.company.project.cucumber.stepdefs.scheduler.RefreshActiveJobsStepDefs.setUp(RefreshActiveJobsStepDefs.java:38)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:498)
  at cucumber.runtime.Utils$1.call(Utils.java:37)
  at cucumber.runtime.Timeout.timeout(Timeout.java:13)
  at cucumber.runtime.Utils.invoke(Utils.java:31)
  at cucumber.runtime.java.JavaHookDefinition.execute(JavaHookDefinition.java:60)
  at cucumber.runtime.Runtime.runHookIfTagsMatch(Runtime.java:223)
  at cucumber.runtime.Runtime.runHooks(Runtime.java:211)
  at cucumber.runtime.Runtime.runBeforeHooks(Runtime.java:201)
  at cucumber.runtime.model.CucumberScenario.run(CucumberScenario.java:40)
  at cucumber.runtime.model.CucumberFeature.run(CucumberFeature.java:165)
  at cucumber.runtime.Runtime.run(Runtime.java:121)
  at cucumber.api.cli.Main.run(Main.java:36)
  at cucumber.api.cli.Main.main(Main.java:18)

To get to this point I followed the following 2 resources, but still no luck getting it working:

The culprit seems to be Cucumber integration into Spring, because when I try the same approach with a plain JUnit @Test method, mocking works as expected.

So could you please tell me what Cucumber or Spring configuration have I missed or misunderstood?

Thanks, Bogdan


Answer:

@MockBean is not ignored, the bean is mocked but it's not injected, so you can inject it via @inject, this works fine for me :

@Inject
@MockBean
private AgentRestClient agentRestClient;

Question:

I need to test my Spring application code by creating a Cucumber integration test. I am using SpringApplicationBuilder to start up my application before the actual logic is triggered and am using the following syntax to do so:-

application = new SpringApplicationBuilder()
    .parent(new Object[]{"classpath:file1.xml", "classpath:file2.xml"})
    .profiles("abc")
    .properties("name:value") [It has 5/6 (name:value) pairs here]*
    .showBanner(false)
    .logStartupInfo(true)
    .headless(true)
    .application()
    .run();

My Spring application starts up correctly. However, it does not get the values for the property (name, value) pairs that I am passing to the SpringApplicationBuilder(). I have tried the following to set them :-

  1. Using name value pairs as above
  2. List item using a HashMap of (name, value) pairs
  3. Creating a ConfigurableEnvironment, retrieving the MutablePropertySources and setting my properties in it.

None of these options are working, so when the application starts up and the code tries to access certain System Property values, it breaks.

Any ideas how this could be fixed.. All the help is greatly appreciated! I need these properties to be Spring properties to make sure the app works correctly. Maybe I can test my code using the Spring props in some other way? If so, how do I do it?


Answer:

You can configure the properties as below:

application = new SpringApplicationBuilder()
    .parent(new Object[]{"classpath:file1.xml", "classpath:file2.xml"})
    .profiles("abc")
    .properties("key1:test1", "key2:test2") 
    .showBanner(false)
    .logStartupInfo(true)
    .headless(true)
    .application()
    .run();

Now, retrieve the properties using @Value annotation:

@Value("${key1}")
String val;

The val variable will be assigned the value test1

Question:

I am having some issues using spring-cassandra-unit, spring-boot, and spring-cucumber. The below configuration works fine for unit tests, but as soon as I add spring-cucumber into the mix and attempt some integration tests, it appears that it flat out ignores my MyCustomOrderedTestExecutionListener and loads spring boot before cassandra, giving me a "NoHostFoundException".

I really could use advice on how to ensure embedded cassandra is loaded first. Any help is greatly appreciated.

The following setup:

@ActiveProfile("INTEGRATION_TEST")
@SpringBootTest
@EmbeddedCassandra(configuration = "cassandra.yaml")
@TestExecutionListeners(
  listeners = MyCustomOrderedTestExecutionListener.class,
  mergeMode = MERGE_WITH_DEFAULTS)
@CassandraDataSet(value = "cql/dataset1.cql", keyspace = "mykeyspace")
public class TestStepDef{

//omitted for brevity

} 

My Custom ordered test execution listener:

public class MyCustomOrderedTestExecutionListener extends AbstractTestExecutionListener {

    @Override
    public void afterTestMethod(TestContext testContext) throws Exception {
       //omitted for brevity
    }

    @Override
    public int getOrder() {
        return  Ordered.HIGHEST_PRECEDENCE;
    }
}

Cucumber Test Runner:

@RunWith(Cucumber.class)
@CucumberOptions(features="resources/features", glue="resources/glue")
public class TestRunner {}

Edit:

Looking at cucumber spring's spring factory, the app context is loaded even before beforeTestClass is executed (beforeTestClass is executed by notifyContextManagerAboutTestClassStarted):

 public void start() {
        if (this.stepClassWithSpringContext != null) {
            this.testContextManager = new CucumberTestContextManager(this.stepClassWithSpringContext);  
        } else if (this.beanFactory == null) {
            this.beanFactory = this.createFallbackContext();
        }

        this.notifyContextManagerAboutTestClassStarted();
        if (this.beanFactory == null || this.isNewContextCreated()) {
            this.beanFactory = this.testContextManager.getBeanFactory();
            Iterator var1 = this.stepClasses.iterator();

            while(var1.hasNext()) {
                Class<?> stepClass = (Class)var1.next();
                this.registerStepClassBeanDefinition(this.beanFactory, stepClass);
            }
        }

        GlueCodeContext.INSTANCE.start();
    }

Going deeper we can see the app context is loaded here:

class CucumberTestContextManager extends TestContextManager {
    public CucumberTestContextManager(Class<?> testClass) {
        super(testClass);
        this.registerGlueCodeScope(this.getContext());
    }

     private ConfigurableApplicationContext getContext() {
    return (ConfigurableApplicationContext)this.getTestContext().getApplicationContext();
     }
...

}

Any advice on how to get around this?


Answer:

Currently Cucumber only invokes TestContextManager.beforeClass and TestContextManager.afterClass. However this happens before every scenario so overriding TestExecutionListener.afterTestClass should do the trick.

Question:

I recently upgrade my test automation project from cucumber 4.2.3 to 5.1.3. When 4.2.3 the tests were running OK(running in parallel). However, once I upgraded to 5.1.3. The runner class initialised with an error.

[ERROR] initializationError(com.my.project.CukeTest)  Time elapsed: 0.008 s  <<< ERROR!
java.lang.NoClassDefFoundError: cucumber/api/event/ConcurrentEventListener
Caused by: java.lang.ClassNotFoundException: cucumber.api.event.ConcurrentEventListene

Below is my runner class.

import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
@CucumberOptions(
    features = {"src/test/resources/features"},
    plugin = {
      "pretty",
      "html:target/cucumber-html-report",
      "json:target/cucumber/cucumber.json",
      "junit:target/cucumber/cucumber.xml",
      "io.qameta.allure.cucumber4jvm.AllureCucumber4Jvm",
      "rerun:target/rerun.txt"
    },
    glue = {"com.my.project.steps", "com.my.project.hook"},
    monochrome = true
    )
public class CukeTest {} 

As you can see there is no direct call on ConcurrentEventListener in the Runner class. I know that since 4.7.x or 4.8.x there are a lot of import have been changed. I've updated those imports already.

Any inputs will be welcome. Thank you in advance.

dependencies that I used.

<cucumber.version>5.1.3</cucumber.version>

        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-core</artifactId>
            <version>${cucumber.version}</version>
        </dependency>

        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java8</artifactId>
            <version>${cucumber.version}</version>
        </dependency>

        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-spring</artifactId>
            <version>${cucumber.version}</version>
        </dependency>

Answer:

You're using the io.qameta.allure.cucumber4jvm.AllureCucumber4Jvm plugin. This plugin isn't compatible with v5.

Question:

I have a Cucumber-Selenium based test written using Spring Boot. The problem is that if I have just one step definition file GoolgeCalcStepDefinition.java then the program works and test passes without any issue but as soon as I added BingSearchStepDefinition.java along with feature file then I get following error.

I googled around on how to configure Spring Boot with Cucumber but most of the examples/articles available online shows only one step definition file.

mvn verify

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.example.TestRunner
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.368 sec <<< FAILURE! - in com.example.TestRunner
initializationError(com.example.TestRunner)  Time elapsed: 0.004 sec  <<< ERROR!
cucumber.runtime.CucumberException: Glue class class com.example.stepdefs.GoogleCalcStepDefinition and class com.example.stepdefs.BingSearchStepDefinition both attempt to configure the spring context. Please ensure only one glue class configures the spring context


Results :

Tests in error:
  TestRunner.initializationError ยป Cucumber Glue class class com.example.stepdef...

Tests run: 1, Failures: 0, Errors: 1, Skipped: 0

[ERROR] There are test failures.

Please refer to I:\pet-projects\junit-cucumber-demo\target\surefire-reports for the individual test results.
[INFO]
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ junit-cucumber-demo ---
[INFO] Building jar: I:\pet-projects\junit-cucumber-demo\target\junit-cucumber-demo-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.0.6.RELEASE:repackage (default) @ junit-cucumber-demo ---
[INFO]
[INFO] --- maven-cucumber-reporting:3.14.0:generate (execution) @ junit-cucumber-demo ---
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. Set system property 'org.apache.logging.log4j.simplelog.StatusLogger.level' to TRACE to show Log4j2 internal initialization logging.
[INFO] About to generate Cucumber report.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.718 s
[INFO] Finished at: 2019-04-14T16:04:55-04:00
[INFO] ------------------------------------------------------------------------

TestRunner.java

@RunWith(Cucumber.class)
@CucumberOptions(
        plugin = {"pretty", "json:target/cucumber-reports/cucumber.json"},
        glue = {"com.example.stepdefs"},
        features = {"src/test/resources/features"})
public class TestRunner {

}

DemoApplicationTests.java

@RunWith(SpringRunner.class)
@SpringBootTest
public abstract class DemoApplicationTests {

}

GoogleCalcStepDefinition.java

@Ignore
public class GoogleCalcStepDefinition extends DemoApplicationTests {

    WebDriver driver;
    GoogleSearchPage googleSearchPage;

    @Given("^I open Google$")
    public void I_open_google() {
        this.driver = BrowserConfig.getWebDriver();
        this.googleSearchPage = PageFactory.initElements(driver, GoogleSearchPage.class);
        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
        driver.get("https://www.google.com");
    }

    @When("^I enter \"([^\"]*)\" in search textbox$")
    public void I_enter_in_search_textbox(String input) {
        googleSearchPage.searchBox.sendKeys(input); //passing 2+2 and 5*5 here
        googleSearchPage.searchBtn.click();
    }

    @Then("^I should get result as \"([^\"]*)\"$")
    public void I_should_get_correct_result(String expectedResult) {
        String result = googleSearchPage.calculatorTextBox.getText();
        assertEquals(result, expectedResult); //Verify that result of 2+2 is 4 and 5*5 is 25
        BrowserConfig.releaseResources(driver);
    }

}

BingSearchStepDefinition.java

@Ignore
public class BingSearchStepDefinition extends DemoApplicationTests {

    WebDriver driver;
    BingSearchPage bingSearchPage;

    @Given("^I open Bing$")
    public void I_open_bing() {
        this.driver = BrowserConfig.getWebDriver();
        this.bingSearchPage = PageFactory.initElements(driver, BingSearchPage.class);
        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
        driver.get("https://www.bing.com");
    }

    @When("^I enter \"([^\"]*)\" in search textbox$")
    public void I_enter_in_search_textbox(String input) {
        bingSearchPage.searchBox.sendKeys(input); //passing searchTerm = Ostrich
        bingSearchPage.searchBtn.click();
    }

    @Then("^I should get result as \"([^\"]*)\"$")
    public void I_should_get_correct_result(String input) {
        String result = driver.getTitle();
        System.out.println("result: " + result);
        assertEquals(result, input); //Verify that result = Bing - <searchTerm>
        BrowserConfig.releaseResources(driver);
    }

}

google.feature

Feature: Check addition in Google calculatorcontent
  In order to verify that Google calculator work correctly
  As a user of Google
  I should be able to get correct addition result

  Background: Do some arithmetic on Google
    Given I open Google

  Scenario: Addition
    When I enter "2+2" in search textbox
    Then I should get result as "4"

  Scenario: Multiplication
    When I enter "5*5" in search textbox
    Then I should get result as "25"

bing.feature

Feature: Check search in Bing
  In order to verify that Bing search works correctly
  As a user of Bing
  I should be able to get correct search result

  Scenario: Bird
    Given I open Bing
    When I enter "Ostrich" in search textbox
    Then I should get page title result as Bing " - Ostrich"

pom.xml

  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

     <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>4.2.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>4.2.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-spring</artifactId>
            <version>4.2.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.seleniumhq.selenium</groupId>
                    <artifactId>selenium-chrome-driver</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-chrome-driver</artifactId>
            <version>2.45.0</version>
            <scope>test</scope>
        </dependency>

Answer:

Both of your runner classes extend 'DemoApplicationTests.java' which is itself a @SpringBootTest. Cucumber cannot determine which SpringBootContext to load when it fires up.. remove the class extends from your stepdefs and have 'TestRunner' extend 'DemoApplicationTests' instead

Question:

I've juste start a sping-boot project version 2.1.6 (using starters : spring-boot-starter-web, spring-boot-starter-test)

And i'm trying to use cucumber for java in version 4.7.1 with following maven dependeces : cucumber-spring, cucumber-junit, cucumber-java8.

When I launch my Acceptance tests, I see when a step is undefined on logs. But no snippets appear

AllAcceptanceTests

@RunWith(Cucumber.class)
@CucumberOptions(plugin = {"pretty", "html:FeaturesReport"},
        features = {"src/test/resources/features"},
        snippets = CucumberOptions.SnippetType.UNDERSCORE)
public class AllAcceptanceTests {
}

My Scenario

  Scenario: Creation from initialisation
    Given name "Jean"
    Given gender "Male"
    Given picture "picture.png"
    Given account identifier 1
    When try to create the profile with these parameters
    Then the profile id = 1 , name = "Jean" , gender = "Male" and picture = "picture.png" is saved

Junit log

  Scenario: Creation from initialisation                                                            # src/test/resources/features/create-profile.feature:9
    Given name "Jean"                                                                                       # ProfileSteps.java:23
    Given gender "Male"                                                                                     # ProfileSteps.java:27
    Given picture "picture.png"                                                                             # ProfileSteps.java:31
    Given account identifier 1                                                                              # null
    When try to create the profile with these parameters                                                    # ProfileSteps.java:37
    Then the profile id = 1 , name = "Jean" , gender = "Male" and picture = "picture.png" is saved # ProfileSteps.java:46

io.cucumber.junit.UndefinedThrowable: The step "existing account in nisfee-account :" is undefined

I'm using all spring-boot default configuration. Did i forget something for having somthing like this in my logs :

You can implement missing steps with the snippets below:

@Given("^today is Sunday$")
public void today_is_Sunday() {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}


Answer:

If you're running through JUnit rather then the CLI or IDEA you have to add the summary plugin explicitly.

@CucumberOptions(plugin = {"summary", "pretty", "html:FeaturesReport"},

Question:

I have a spring boot application with cucumber. I would like to perform certain events within the test BEFORE the start-SmartLifecycle event of the beans is called.

Given Something that needs to happen before beans are started
And Beans start up now
Then Life is good

Is there any way to achieve this? By default, it looks like Spring initializes and starts all the beans before any Cucumber statements are executed.

Example:

class Context {
  @Bean
  SomeBean someBean() { return new SomeBean(); }
}

class SomeBean implements SmartLifecycle {
  @Override
  void start() {
    // some meaningful work that depends on setup that needs to be done beforehand
  }
  // rest of interface implementation
}

Cucumber definitions file:

 @ContextConfiguration(classes = Context.class)
 class CucumberFeatures {
   @Autowired
   private SomeBean someBean;

   @Given("Something that needs to happen before beans are started")
   public void something() {
     // ...
   }

   @Given("Beans start up now")
   public void beansStarted() {
     // This should start beans in their defined order now
   }

   @Then("Life is good")
   public void lifeIsGood() { ... }
 }

Answer:

You can't test a dependency injection container while using that same container to inject dependencies into your test. Injecting dependencies requires the application context to have been refreshed already.

So you have to create an instance of the ApplicationContext manually and manage it's life-cycle yourself.

Question:

My Spring-boot Application worked perfectly until I began fiddling with spring boot and cucumber testing. Can't say exactly what went wrong but from my research it seems to be a library dependency mismatch in my pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>EmploymentBootCamp</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>EmploymentBootCamp</name>
<description>Demo project for Spring Boot Employment Boot Camp</description>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <start-class>EmploymentBootCamp.demo.EmploymentBootCampApplication</start-class>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.derby</groupId>
        <artifactId>derby</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>info.cukes</groupId>
        <artifactId>cucumber-java</artifactId>
        <version>1.2.4</version>
    </dependency>
    <dependency>
        <groupId>info.cukes</groupId>
        <artifactId>cucumber-junit</artifactId>
        <version>1.2.4</version>
    </dependency>
    <dependency>
        <groupId>info.cukes</groupId>
        <artifactId>cucumber-spring</artifactId>
        <version>1.2.4</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
        <version>2.0.1.RELEASE</version>
        <scope>test</scope>
        <type>jar</type>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>3.11.0</version>
    </dependency>

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.0.1.RELEASE</version>
            <configuration>
                <mainClass>EmploymentBootCamp.demo.EmploymentBootCampApplication</mainClass>
                <layout>ZIP</layout>
            </configuration>
        </plugin>
    </plugins>
</build>

The error I get is down below

java.lang.NoClassDefFoundError: org/springframework/dao/support/PersistenceExceptionTranslator at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_152] at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[na:1.8.0_152] at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[na:1.8.0_152] at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) ~[na:1.8.0_152] at java.net.URLClassLoader.access$100(URLClassLoader.java:73) ~[na:1.8.0_152] at java.net.URLClassLoader$1.run(URLClassLoader.java:368) ~[na:1.8.0_152] at java.net.URLClassLoader$1.run(URLClassLoader.java:362) ~[na:1.8.0_152] at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_152] at java.net.URLClassLoader.findClass(URLClassLoader.java:361) ~[na:1.8.0_152] at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_152] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338) ~[na:1.8.0_152] at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_152] at org.springframework.data.jpa.util.BeanDefinitionUtils.(BeanDefinitionUtils.java:55) ~[spring-data-jpa-2.0.6.RELEASE.jar:2.0.6.RELEASE] at org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor.postProcessBeanFactory(EntityManagerBeanDefinitionRegistrarPostProcessor.java:54) ~[spring-data-jpa-2.0.6.RELEASE.jar:2.0.6.RELEASE] at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:284) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:179) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:694) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) [spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:395) [spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:327) [spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255) [spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243) [spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at EmploymentBootCamp.demo.EmploymentBootCampApplication.main(EmploymentBootCampApplication.java:10) [classes/:na] Caused by: java.lang.ClassNotFoundException: org.springframework.dao.support.PersistenceExceptionTranslator at java.net.URLClassLoader.findClass(URLClassLoader.java:381) ~[na:1.8.0_152] at java.lang.ClassLoader.loadClass(ClassLoader.java:424) ~[na:1.8.0_152] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338) ~[na:1.8.0_152] at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ~[na:1.8.0_152] ... 25 common frames omitted


Answer:

you can safely remove this dependency:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <scope>test</scope>
</dependency>

via spring-boot-starter-data-jpa you already have a transitive dependency to spring-tx.

By explicitly binding spring-tx to the test-scope, you remove it from the compile-scope (mavens default scope)

Question:

I am running a Springboot application with jpa. I am trying to set up an integration-test based on Cucumber. When in my test I try to access the repo, I get an 'org.hibernate.LazyInitializationException' (With a message "No Session".). This only happens in my integration-test, but not in the real application. A workaround would be to put @Transactional on the method which does the call, but this does not work if I execute in new threads.

My first question would be: Why doesn't it work withouth @Transactional annotation? My second would be: Why doesn't it work with @Transactional annotation in new threads?

Here is a simplified version of my code:

The cucumber test:

@RunWith(Cucumber.class)
@CucumberOptions(features = "src/test/resources/some-integration-test.feature")
public class IntegrationTests {}

The cucumber-steps:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class IntegrationSteps {

    @Autowired
    SomeRepo repo;

    @When("two updates happen at the same time")
    public void twoUpdatesHappenAtTheSameTime() {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        executorService.execute(set("Thread 1"));

        executorService.execute(set("Thread 2"));

        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.MINUTES);
    }

    public void set(String someThing) {
        Some some = repo.getOne(1234);
        repo.setSomeThing(someThing);
        repo.save(some);
    }
}

And the repo:

@Repository
public interface SomeRepo extends JpaRepository<Some, Integer> {}

Answer:

The problem seems to be with getOne(). With getOne() you only get a reference to the entity. No real call to the database is executed until one tries to access the entity's fields. Normally these fields are loaded lazy when one uses getOne(), but for some reason (which is still unclear to me) this does not work in the SpringBootTest.

I've found two solutions for this issue:

  1. Annotate the test with @Transactional. This way you will have a context to load the entity. The downside is that you still don't seem to get the most recent version of your entity. In my case I updated the entity in my code, while in test code the update was not present in the entity (even though update happened before the getOne() call).
  2. Don't use getOne(), but findById() instead. The downside is that findById() is eagerly loaded, but as we only use this for our test, it will not affect the performance of our app.

Question:

In a Spring Boot REST application, I want to check with Cucumber-jvm that the returned JSON is exactly what I expect. However, because I have to use double quotation around JSON key names, Cucumber cannot detect the correct step definition method and thus the test cannot pass.

Here is the expected JSON result:

{"fields":[],"errorMsg":"BIN not found"}

Cucumber step definition:

Given bin number is <bin>
When binlookup searches with this bin number
Then binlookup returns <result> and status code <code>

Examples: 
  | bin      | result                                                       | code |
  | "222222" | "{\"fields\":[\"bin\"]\,\"errorMsg\":\"Invalid Argument\"}"   | 404  |

The corresponding method:

@Then("^binlookup returns \"([^\"]*)\" and status code \\d$")
public void binlookup_returns_and_status_code(String result, Integer code) throws Exception {
    assertThat(this.results.getResponse().getContentType()).isEqualTo(MediaType.APPLICATION_JSON_UTF8_VALUE);
    assertThat(this.results.getResponse().getStatus()).isEqualTo(code);
    assertThat(this.results.getResponse().getContentAsString().length()).isNotEqualTo(0);
    assertThat(this.results.getResponse().getContentAsString()).isEqualTo(result);
}

When running the test, I do have correct returned JSON:

{"fields":["bin"],"errorMsg":"Invalid Argument"}

But I see test errors and Cucumber cannot detect my method, and gives me tips like:

You can implement missing steps with the snippets below:

@Then("binlookup returns {string}\\:[],\\{string}\\:\\{string} and status code {int}")
public void binlookup_returns_and_status_code(String string, String string2, String string3, Integer int1) {
    // Write code here that turns the phrase above into concrete actions
    throw new PendingException();
}

Obviously, it pairs the first " with the first escaped " and sees {\"fields as the first parameter, but it is wrong.

But, I cannot quote the JSON string with ' ' because it will not be the case.

What can I do?


If it is impossible, how can I verify the JSON has the data I expect?


Answer:

The cucumber's stepdefs is all about regex. The arguments are captured using capture groups, you only need to use a regex that match the json.

I think it would work to you:

@Then("^binlookup returns \"(.*)\" and status code \\d$")

The \"(.*)\" regex will capture everything inside double quotes.

The entire regex is: "binlookup returns ", followed by everything inside double quotes (the \"(.*)\" regex), followed by " and status code ", followed by a number (the \d regex).

And in the stepDef file:

Examples: 
| bin      | result code |
| "222222" | "{"fields":["bin"],"errorMsg":"Invalid Argument"}"   | 404  |

Note that you don't need to escape the double quotes inside json using this approach.