Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cucumber with Spring Boot 1.4: Dependencies not injected when using @SpringBootTest and @RunWith(SpringRunner.class)

I am writing a new app and trying to do BDD using cucumber and Spring Boot 1.4. Working code is as shown below:

@SpringBootApplication
public class Application {
    @Bean
    MyService myService() {
        return new MyService();
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

public class MyService {}

Test code is as shown below:

@RunWith(Cucumber.class)
public class RunFeatures {}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class, loader = SpringApplicationContextLoader.class)
public class MyStepDef {
    @Autowired
    MyService myService;

    @Given("^Some initial condition$")
    public void appIsStarted() throws Throwable {
        if (service == null) throw new Exception("Dependency not injected!");
        System.out.println("App started");
    }

    @Then("^Nothing happens$")
    public void thereShouldBeNoException() throws Throwable {
        System.out.println("Test passed");
    }
}

Feature file is as shown below:

Feature: Test Cucumber with spring
    Scenario: First Scenario
        Given Some initial condition
        Then Nothing happens

When I run the above as is, all works well and dependency (MyService) is injected into MyStepDef with no issues.

If I replace this code:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class, loader = SpringApplicationContextLoader.class)

With the code below (New way to handle it in Spring Boot 1.4):

@RunWith(SpringRunner.class)
@SpringBootTest

Then the dependency (MyService) never gets injected. Am I missing something perhaps?

Thanks in advance for your help!!!

like image 509
Aliyu Fonyuy Avatar asked Aug 08 '16 18:08

Aliyu Fonyuy


People also ask

What does @RunWith SpringRunner class mean?

@RunWith(SpringRunner. class) tells JUnit to run using Spring's testing support. SpringRunner is the new name for SpringJUnit4ClassRunner , it's just a bit easier on the eye. @SpringBootTest is saying “bootstrap with Spring Boot's support” (e.g. load application.

What is cucumber PicoContainer?

Cucumber scans your classes with step definitions in them, passes them to PicoContainer, then asks it to create new instances for every scenario.

What is CucumberOptions?

What is Cucumber Options? In layman language, @CucumberOptions are like property files or settings for your test. Basically @CucumberOptions enables us to do all the things that we could have done if we have used cucumber command line.


2 Answers

I had the same problem. The comment from above directed me to the solution

The problematic code in cucumber-spring seems to be this github.com/cucumber/cucumber-jvm/blob/master/spring/src/main‌​/…

After adding the annotation @ContextConfiguration the tests are working as expected.

So what i've got is the following...

@RunWith(Cucumber.class)
@CucumberOptions(plugin = {"json:target/cucumber.json", "pretty"}, features = "src/test/features")
public class CucumberTest {
}

@ContextConfiguration
@SpringBootTest
public abstract class StepDefs {
}

public class MyStepDefs extends StepDefs {

    @Inject
    Service service;

    @Inject
    Repository repository;

    [...]

}

I hope this helps you further

like image 160
judomu Avatar answered Oct 12 '22 01:10

judomu


I got it working with Spring Boot 1.5.x and 2.0 and then wrote a blog post to try to clarify this since it's tricky.

First, even if it's obvious, you need to have the right dependencies included in your project (being cucumber-spring the important one here). For example, with Maven:

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-java</artifactId>
    <version>2.3.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-junit</artifactId>
    <version>2.3.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-spring</artifactId>
    <version>2.3.1</version>
    <scope>test</scope>
</dependency>

Now, the important part to make it work, summarized:

  • The entry point to your test should be a class annotated with @RunWith(Cucumber.class.
  • This class will use the steps definitions, which are normally in a separated class with annotated methods (@Given, @When, @Then, etc.).
  • The trick is that this class should extend a base class annotated with @SpringBootTest, @RunWith(SpringRunner.class) and any other configuration you need to run your test with Spring Boot. For instance, if you're implementing an integration test without mocking other layers, you should add the webEnvironment configuration and set it to RANDOM_PORT or DEFINED_PORT.

See the diagram and the code skeleton below.

TPD - Using DI with Cucumber in Spring Boot

The entry point:

@RunWith(Cucumber.class)
@CucumberOptions(features = "src/test/resources/features/bag.feature", plugin = {"pretty", "html:target/cucumber"})
public class BagCucumberIntegrationTest {
}

The Spring Boot base test class:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class SpringBootBaseIntegrationTest {
}

The step definitions class:

@Ignore
public class BagCucumberStepDefinitions extends SpringBootBaseIntegrationTest {
  // @Given, @When, @Then annotated methods
}

This is what you need to make DI work. For the full code example, just check my blog post or the code in GitHub.

like image 30
Moisés Avatar answered Oct 12 '22 00:10

Moisés