Skip to content

Commit

Permalink
Setup step delcarations for cucumber-java-lambda
Browse files Browse the repository at this point in the history
  • Loading branch information
mpkorstanje committed Jul 9, 2022
1 parent cbddfd0 commit 17d5ab2
Show file tree
Hide file tree
Showing 10 changed files with 983 additions and 1 deletion.
253 changes: 253 additions & 0 deletions java-lambda/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
Cucumber Java8
==============

Provides lambda based step definitions. To use add the `cucumber-java8` dependency to your pom.xml:

```xml
<dependencies>
[...]
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java8</artifactId>
<version>${cucumber.version}</version>
<scope>test</scope>
</dependency>
[...]
</dependencies>
```

## Step Definitions

Declare a step definition calling a method in the constructor of the glue class.
For localized methods import the interface from `io.cucumber.java8.<ISO2 Language Code>`

Data tables and Docstrings from Gherkin can be accessed by using a `DataTable`
or `DocString` object as the last parameter.

```java
package com.example.app;

import io.cucumber.java8.En;
import io.cucumber.datatable.DataTable;
import io.cucumber.docstring.DocString;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class StepDefinitions implements En {

private RpnCalculator calc;

public RpnCalculatorSteps() {
Given("a calculator I just turned on", () -> {
calc = new RpnCalculator();
});

When("I add {int} and {int}", (Integer arg1, Integer arg2) -> {
calc.push(arg1);
calc.push(arg2);
calc.push("+");
});

Then("the result is {double}", (Double expected) -> assertEquals(expected, calc.value()));

Given("the previous entries:", (DataTable dataTable) -> {
List<Entry> entries = dataTable.asList(Entry.class);
...
});

Then("the calculation log displays:", (DocString docString) -> {
...
});
}
}
```

## Hooks

Declare hooks that will be executed before/after each scenario/step by calling a
method in the constructor. The method may declare an argument of type `io.cucumber.java8.Scenario`.

* `Before`
* `After`
* `BeforeStep`
* `AfterStep`

## Transformers

### Parameter Type

Step definition parameter types can be declared by using `ParameterType`.

```java
package com.example.app;

import io.cucumber.java8.En;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class StepDefinitions implements En {

public StepDefinitions() {
ParameterType("amount", "(\\d+\\.\\d+)\\s([a-zA-Z]+)", (String[] values) ->
new Amount(new BigDecimal(values[0]), Currency.getInstance(values[1])));
}
}
```

### Data Table Type

Data table types can be declared by calling `DataTableType` in the constructor.
Depending on the lambda type this will be either a:
* `String` -> `io.cucumber.datatable.TableCellTranformer`
* `Map<String,String>` -> `io.cucumber.datatable.TableEntry`
* `List<String>` -> `io.cucumber.datatable.TableRow`
* `DataTable` -> `io.cucumber.datatable.TableTransformer`

```java
package com.example.app;

import io.cucumber.java8.En;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class StepDefinitions implements En {

public StepDefinitions() {
DataTableType((Map<String, String> row) -> new Grocery(
row.get("name"),
Price.fromString(row.get("price"))
));
}
}
```

### Default Transformers

Default transformers allow you to specific a transformer that will be used when
there is no transform defined. This can be combined with an object mapper like
Jackson to quickly transform well known string representations to Java objects.

* `DefaultParameterTransformer`
* `DefaultDataTableEntryTransformer`
* `DefaultDataTableCellTransformer`

For a full list of transformations that can be achieved with data table types
see [cucumber/datatable](https://github.com/cucumber/cucumber/tree/master/datatable)

```java
package com.example.app;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.cucumber.java8.En;

public class StepDefinitions implements En {

public StepDefinitions() {
final ObjectMapper objectMapper = new ObjectMapper();

DefaultParameterTransformer((fromValue, toValueType) ->
objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType))
);
}
}
```

### Empty Cells

Data tables in Gherkin can not represent null or the empty string unambiguously.
Cucumber will interpret empty cells as `null`.

Empty string be represented using a replacement. For example `[empty]`.
The replacement can be configured providing the `replaceWithEmptyString`
argument of `DataTableType`, `DefaultDataTableCellTransformer` and
`DefaultDataTableEntryTransformer`. By default no replacement is configured.

```gherkin
Given some authors
| name | first publication |
| Aspiring Author | |
| Ancient Author | [blank] |
```

```java
package com.example.app;

import io.cucumber.datatable.DataTable;

import io.cucumber.java8.En;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class StepDefinitions implements En {

public StepDefinitions() {
DataTableType("[blank]", (Map<String, String> entry) -> new Author(
entry.get("name"),
entry.get("first publication")
));

Given("some authors", (DataTable authorsTable) -> {
List<Author> authors = authorsTable.asList(Author.class);
// authors = [Author(name="Aspiring Author", firstPublication=null), Author(name="Ancient Author", firstPublication=)]

});
}
}
```

# Transposing Tables

A data table can be transposed by calling `.transpose()`. This means the keys
will be in the first column rather then the first row.

```gherkin
Given the user is
| firstname | Roberto |
| lastname | Lo Giacco |
| nationality | Italian |
```

And a data table type to create a User

```java
package com.example.app;

import io.cucumber.datatable.DataTable;

import io.cucumber.java8.En;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class StepDefinitions implements En {

public StepDefinitions() {
DataTableType((Map<String, String> entry) -> new User(
entry.get("firstname"),
entry.get("lastname")
entry.get("nationality")
));

Given("the user is", (DataTable authorsTable) -> {
User user = authorsTable.transpose().asList(User.class);
// user = User(firstname="Roberto", lastname="Lo Giacco", nationality="Italian")
});
}
}
```
80 changes: 80 additions & 0 deletions java-lambda/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<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>

<parent>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-jvm</artifactId>
<version>7.3.3-SNAPSHOT</version>
</parent>

<artifactId>cucumber-java-lambda</artifactId>
<packaging>jar</packaging>
<name>Cucumber-JVM: Java Lambda</name>

<properties>
<project.Automatic-Module-Name>io.cucumber.lambda</project.Automatic-Module-Name>
<apiguardian-api.version>1.1.2</apiguardian-api.version>
<junit-jupiter.version>5.8.2</junit-jupiter.version>
<typetools.version>0.6.3</typetools.version>
<assertj.version>3.22.0</assertj.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-bom</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${junit-jupiter.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-core</artifactId>
</dependency>
<dependency>
<groupId>org.apiguardian</groupId>
<artifactId>apiguardian-api</artifactId>
<version>${apiguardian-api.version}</version>
</dependency>
<dependency>
<groupId>net.jodah</groupId>
<artifactId>typetools</artifactId>
<version>${typetools.version}</version>
</dependency>

<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
</dependency>
</dependencies>

</project>
30 changes: 30 additions & 0 deletions java-lambda/src/main/java/io/cucumber/lambda/Invoker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.cucumber.lambda;

import io.cucumber.core.backend.CucumberBackendException;
import io.cucumber.core.backend.CucumberInvocationTargetException;
import io.cucumber.core.backend.Located;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

final class Invoker {

private Invoker() {

}

static Object invoke(Located located, Object target, Method method, Object... args) {
boolean accessible = method.canAccess(target);
try {
method.setAccessible(true);
return method.invoke(target, args);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new CucumberBackendException("Failed to invoke " + located.getLocation(), e);
} catch (InvocationTargetException e) {
throw new CucumberInvocationTargetException(located, e);
} finally {
method.setAccessible(accessible);
}
}

}
Loading

0 comments on commit 17d5ab2

Please sign in to comment.