# Lab 06 : BDD with Cucumber framework


# Useful commands 

```mvn test```


<br>

# 1. Cucumber tests

## 1.1 Dependencies
POM dependencies (or use the cucumber archetype ```io.cucumber:cucumber-archetype```):   

```xml
<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-java</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-junit-platform-engine</artifactId>
    <scope>test</scope>
</dependency>
```
<br>


## 1.2 .feature file   

**Location:** ```src/test/resources/ua/tqs/example.feature```

**Example:**
```txt
Feature: Basic Arithmetic

    Background: A Calculator
        Given I have a RPN calculator

    Scenario: Add two numbers
        When I enter 1
        And I enter 2
        And I press "+"
        Then the result should be 3
```

<br>

## 1.3 Steps definition

**Location:** ```src/test/java/ua/tqs/StepDefinitions.java```

**Example:**
```java
public class StepDefinitions {

    private RpnCalculator calculator;

    @Given("I have a RPN calculator")
    public void i_have_a_calculator() {
        calculator = new RpnCalculator();
    }

    @When("I enter {int}")
    public void i_enter(Integer value) {
        calculator.pushOperand(value);
    }

    @When("I press {string}")
    public void i_press(String operator) {
        calculator.applyOperator(operator);
    }

    @Then("the result should be {int}")
    public void the_result_should_be(Integer expected) {
        int actual = calculator.topValue();
        assertEquals(expected.intValue(), actual);
    }
}
```

Don't forget to implement the calculator into your project   
   
Run the tests with 
```mvn test```

<br>

## 1.4 Custom parameters

By declaring **iso8601Date** in a class, it can now be used in our steps definition  

```java
public class CustomParameterTypes {

    @ParameterType("\\d{4}-\\d{2}-\\d{2}")
    public LocalDate iso8601Date(String value) {
        return LocalDate.parse(value, DateTimeFormatter.ISO_LOCAL_DATE);
    }
}
```
<br>


### Example of a table in .feature
```
 Background:
    Given the following books exist:
      | title            | author        | published   |
      | One good book    | Anonymous     | 2013-03-12  |
      | Some other book  | Tim Tomson    | 2020-08-23  |
      | Java for All     | Ana Writer    | 2018-05-10  |

  Scenario: Search books by author with results
    When the customer searches for books written by "Tim Tomson"
    Then the following books should be found:
      | title           |
      | Some other book |

  Scenario: Search books by author with no results
    When the customer searches for books written by "Nobody"
    Then no books should be found

  Scenario: Search books between two publication dates
    When the customer searches for books published between 2013-01-01 and 2014-12-31
    Then the following books should be found:
      | title         |
      | One good book |
```

```java
@Given("the following books exist:")
    public void the_following_books_exist(DataTable table) {
        List<Map<String, String>> rows = table.asMaps(String.class, String.class);

        for (Map<String, String> row : rows) {
            String title = row.get("title");
            String author = row.get("author");
            LocalDate publishedDate = LocalDate.parse(row.get("published"));

            Book book = new Book(
                    title,
                    author,
                    publishedDate.atStartOfDay()      // LocalDateTime
            );

            library.addBook(book);
        }
    }


    @When("the customer searches for books written by {string}")
    public void the_customer_searches_for_books_written_by(String author) {
        results = library.findBooksByAuthor(author);
    }


    @When("the customer searches for books published between {iso8601Date} and {iso8601Date}")
    public void the_customer_searches_for_books_published_between(LocalDate from, LocalDate to) {

        LocalDateTime start = from.atStartOfDay();
        LocalDateTime end = to.plusDays(1).atStartOfDay().minusNanos(1); // inclusive end date

        results = library.findBooks(start, end);
    }


   @Then("the following books should be found:")
    public void the_following_books_should_be_found(DataTable table) {
        //each row after the header becomes a map: { "title" -> "Some other book" }
        List<Map<String, String>> rows = table.asMaps(String.class, String.class);

        List<String> expectedTitles = new ArrayList<>();
        for (Map<String, String> row : rows) {
            expectedTitles.add(row.get("title"));
        }

        List<String> actualTitles = new ArrayList<>();
        for (Book b : results) {
            actualTitles.add(b.getTitle());
        }

        assertEquals("Number of books does not match",
                expectedTitles.size(), actualTitles.size());

        for (String title : expectedTitles) {
            assertTrue("Expected book not found: " + title, actualTitles.contains(title));
        }
    }

    
    @Then("no books should be found")
    public void no_books_should_be_found() {
        assertTrue("Expected no books, but found " + results.size(), results.isEmpty());
    }
```