How it works...

If you feel a bit lost after looking at all this code and following along without having a full understanding of what exactly is going on, here you will find a detailed breakdown of everything that we did.

Let's start with a quick overview of what Step Definitions are. As the Cucumber framework uses the Gherkin feature document files in order to describe the business rules that are to be tested, which are represented in the form of English-like sentence statements, these need to be translated into executable code. This is the job of the Step Definition classes. Every step in a defined feature scenario needs to be matched to a method in a Step Definition class that will execute it. This matching is done by declaring a regular expression in the step annotations above the methods. The regex contains the matching groups that Cucumber uses so as to extract the method arguments and pass them to the executing method.

In RepositoryStepdefs, we can see this in the following method:

@Given("^([^"]*) fixture is loaded$") 
public void data_fixture_is_loaded(String fixtureName) {...} 

The @Given annotation contains the regular expression that matches the Given packt-books fixture is loaded text, loaded from repositories.feature file, and extracts the packt-books text from the pattern, which is then passed as a fixtureName argument to the method. The @When and @Then annotations work on exactly the same principle. So, in effect, what the Cucumber framework does is it matches the English-like worded rules from the feature files to the matched patterns of the executing methods and extracts parts of the rules as arguments to the matched methods.

More information on Gherkin and how to use it can be found at https://cukes.info/docs/reference#gherkin.

With the basic Cucumber overview explained, let's shift our focus to how the tests integrate with Spring Boot and are configured.

It all starts with the driver harness class, which in our case is RunCukeTests. This class itself does not contain any tests, but it has two important annotations that stitch things together, @RunWith(Cucumber.class) and @CucumberOptions:

  • @RunWith(Cucumber.class): This is a JUnit annotation that indicates that JUnit runner should use the Cucumber feature files to execute the tests.

@CucumberOptions: This provides additional configuration for Cucumber:

  • plugin={"pretty", "html:build/reports/cucumber"}: This tells Cucumber to generate its reports in HTML format in the build/reports/cucumber directory.
  • glue = {"cucumber.api.spring", "classpath:com.example.bookpub"}: This is a very important setting, as it tells Cucumber which packages to load and from where to load them during the execution of the tests. The cucumber.api.spring package needs to be present in order to take advantage of the cucumber-spring integration library, and the com.example.bookpub package is the location of our Step Definition implementation classes.
  • monochrome = true: This tells Cucumber not to print the output with the ANSI color as we integrate with JUnit, as it will not look correct in the saved console output files.
A complete list of the options can be found at https://cukes.info/docs/reference/jvm#list-all-options.

Now let's look at the RepositoryStepdefs class. It starts with the following annotations at the class level:

  • @WebAppConfiguration instructs Spring that this class needs WebApplicationContext to be initialized, and it will be used for testing purposes during the execution
  • @ContextConfiguration(classes = BookPubApplication.class and loader = SpringBootContextLoader.class) instruct Spring to use the BookPubApplication class as a configuration for the Spring application context, as well as to use the SpringBootContextLoader class from Spring Boot in order to bootstrap the testing harness
It is important to note that these annotations have to match  all the Step Definition classes, or only one of the classes will be annotated with the @ContextConfiguration annotation to wire in the Spring support for the Cucumber test.

As the cucumber-spring integration does not know about Spring Boot but only about Spring, we can't use the @SpringBootTest meta-annotation. We have to resort to using only the annotations from Spring in order to stitch things together. Thankfully, we don't have to go through many hoops, but just declare the exact annotation that SpringBootTest facades by passing the desired configuration classes and loader.

Once the proper annotations are in place, Spring and Spring Boot will take over and provide us with the same convenience of autowiring beans as dependencies of our Step Definition classes.

One interesting characteristic of the Cucumber tests is the instantiation of a new instance of the Step Definition class for every execution of a Scenario. Even though the method namespace is global—meaning that we can use the methods that are declared in the different Step Definition classes—they operate on states defined in them and are not directly shared. It is, however, possible to @Autowire an instance of another Step Definition in a different Step Definition instance and rely on public methods or fields to access and mutate the data.

As a new instance gets created per scenario, the definition classes are stateful and rely on internal variables to keep a state among transitions from assertion to assertion. For example, in the @When annotated method, a particular state gets set, and in the @Then annotated method, a set of assertions on that state get evaluated. In our example of the RepositoryStepdefs class, we will internally set the state of the loadedBook class variable in its searching_for_book_by_isbn(...) method, which later gets used to assert on so as to verify the match of the book's title in the book_title_will_be(...) method afterwards. Due to this, if we mix the rules from the different definition classes in our feature files, the internal states would not be accessible among the multiple classes.

When integrating with Spring, one can use the injection of the mocked objects—as we have seen in MockPublisherRepositoryTests from one of our previous examples—and can have the shared @Given annotated method be used to set up the particular behavior of the mock for the given test. Then we can use the same dependency instance and inject it into another definition class that can be used in order to evaluate the @Then annotated assertion methods.

Another approach is the one that we saw in the second definition class, RestfulStepdefs, where we injected BookRepository. However, in restful.feature, we will be using the Given packt-books fixture is loaded behavior declaration that translates to the invocation of data_fixture_is_loaded method from the RepositoryStepdefs class, which shares the same instance of the injected BookRepository object, inserting the packt-books.sql data into it.

If we were to have a need to access the value of the loadedBook field from the RepositoryStepdefs instance inside the RestfulStepdefs class, we could declare the @Autowired RepositoryStepdefs field inside RestfulStepdefs and make the loadedBook field public instead of private to make it accessible to the outside world.

Another neat feature of the Cucumber and Spring integration is the use of the @txn annotation in the feature files. This tells Spring to execute the tests in a transaction wrapper, reset the database between the test executions, and guarantee a clean database state for every test.

Due to the global method namespace among all the Step Definition classes and test behavior defining feature files, we can use the power of Spring injection to our advantage so as to reuse the testing models and have a common setup logic for all of the tests. This makes the tests behave similarly to how our application would function in a real production environment.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.94.251