Skip to content

Commit

Permalink
[Java] Allow parameter types access to test context (#1677)
Browse files Browse the repository at this point in the history
# Summary

By annotating methods parameter and data table types can be defined as 
part of the Glue. This enables them to access the test context and makes
them eligible for dependency injection. Additionally the type registry is
now created for each feature. This means the language of the feature will
be used to convert numbers.

## Details

## Parameter and DataTable Type

Introduces the `@ParameterType` and `@DataTableType` annotations. This
allows parameter and datatable types to be mapped to objects which can
only be created by services inside the test context.

For example in this scenario
```gherkin
Given the awesome catalog
When a user places the awestruck eels in his basket
Then you will be shocked at what happened next
```

We are now able to look up the "awestruck eels" in the "awesome" catalog.

```java
private final Catalog catalog;

@ParameterType(".*")
public Product product(String name) {
  return catalog.findProductByName(name);
}
```
## Default Transformer

It is now also possible to register default transformers using
annotations. 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`

 ```java
package com.example.app;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.cucumber.java.DefaultDataTableCellTransformer;
import io.cucumber.java.DefaultDataTableEntryTransformer;
import io.cucumber.java.DefaultParameterTransformer;

import java.lang.reflect.Type;

public class DataTableSteps {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @DefaultParameterTransformer
    @DefaultDataTableEntryTransformer
    @DefaultDataTableCellTransformer
    public Object defaultTransformer(Object fromValue, Type toValueType) {
        return objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType));
    }
}
```

## Localization

Some languages uses comma's rather then points to separate decimals.
Previously to parse these properly you'd have to use
`TypeRegistryConfigurer.locale` to set this globally. When not explicitly
provided Cucumber will now take the language from the feature file. This
makes the following work without additional configuration:

```gherkin
# language: fr
Fonctionnalité: Concombres fractionnaires

  Scénario: dans la ventre
    Étant donné j'ai 5,5 concombres fractionnaires
```
```java
@Étantdonné("j'ai {bigdecimal} concombres fractionnaires")
public void jAiConcombresFractionnaires(BigDecimal arg0) {
    assertThat(arg0, is(new BigDecimal("5.5")));
}
```

# Motivation & Context

Fixes #851.
Fixes #1458.
  • Loading branch information
mpkorstanje authored Jul 28, 2019
2 parents 0e41a1a + 3e6da03 commit ce0a6b8
Show file tree
Hide file tree
Showing 110 changed files with 3,198 additions and 864 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO
- Use `io.cucumber.core.cli.Main` instead
* [Core] Deprecate `cucumber.api.Scenario`
- Use `io.cucumber.core.api.Scenario` instead
* [Java] Deprecate `cucumber.api.java.*`
- Use `io.cucumber.java.*` instead
* [Java] Deprecate `cucumber.api.java8.*`
- Use `io.cucumber.java8.*` instead
* [JUnit] Deprecate `cucumber.api.junit.Cucumber`
- Use `io.cucumber.junit.Cucumber` instead.
* [TestNG] Deprecate `cucumber.api.testng.TestNGCucumberRunner`
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
[![Coverage Status](https://coveralls.io/repos/github/cucumber/cucumber-jvm/badge.svg?branch=master)](https://coveralls.io/github/cucumber/cucumber-jvm?branch=master)

Cucumber-JVM is a pure Java implementation of Cucumber.
You can [run](https://docs.cucumber.io/cucumber/api/#running-cucumber) it with
You can [run](https://cucumber.io/docs/cucumber/api/#running-cucumber) it with
the tool of your choice.

Cucumber-JVM also integrates with all the popular
[Dependency Injection containers](https://docs.cucumber.io/installation/java/#dependency-injection).
[Dependency Injection containers](https://cucumber.io/docs/installation/java/#dependency-injection).

## Getting started
* [Installation](https://docs.cucumber.io/installation/java/)
* [Installation](https://cucumber.io/docs/installation/java/)
* [Documentation](https://cucumber.io/docs)
* [Hello World project](https://github.com/cucumber/cucumber-java-skeleton)

Expand Down
1 change: 1 addition & 0 deletions cdi2/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<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>
<properties>
<project.Automatic-Module-Name>io.cucumber.cdi2</project.Automatic-Module-Name>
<openwebbeans.version>2.0.10</openwebbeans.version>
</properties>

Expand Down
1 change: 1 addition & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<name>Cucumber-JVM: Core</name>

<properties>
<project.Automatic-Module-Name>io.cucumber.core</project.Automatic-Module-Name>
<!-- version 2.4.0 does not work on Java 7 -->
<jsoup.version>1.12.1</jsoup.version>
<xmlunit.version>1.6</xmlunit.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
@API(status = API.Status.STABLE)
public interface TypeRegistryConfigurer {
/**
* @return The locale to use.
* @return The locale to use, or null when language from feature file should be used.
*/
Locale locale();
default Locale locale() {
return null;
}

/**
* Configures the type registry.
*
* @param typeRegistry The new type registry.
*/
void configureTypeRegistry(TypeRegistry typeRegistry);
Expand Down
10 changes: 6 additions & 4 deletions core/src/main/java/io/cucumber/core/backend/Backend.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@
@API(status = API.Status.STABLE)
public interface Backend {
/**
* Invoked once before all features. This is where stepdefs and hooks should be loaded.
*
* @param glue Glue that provides the stepdefs to be executed.
* Invoked once before all features. This is where steps and hooks should be loaded.
*
* @param glue Glue that provides the steps to be executed.
* @param gluePaths The locations for the glue to be loaded.
*/
void loadGlue(Glue glue, List<URI> gluePaths);

/**
* Invoked before a new scenario starts. Implementations should do any necessary
* setup of new, isolated state here.
* setup of new, isolated state here. Additional scenario scoped step definitions
* can be loaded here. These step definitions should implement
* {@link io.cucumber.core.runner.ScenarioScoped}
*/
void buildWorld();

Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/io/cucumber/core/backend/Container.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public interface Container {
* Collects glue classes in the classpath. Called once on init.
*
* @param glueClass Glue class containing cucumber.api annotations (Before, Given, When, ...)
* @return true if stepdefs and hooks in this class should be used, false if they should be ignored.
* @return true if steps and hook definitions in this class should be used, false if they should be ignored.
*/
boolean addClass(Class<?> glueClass);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.cucumber.core.backend;

import io.cucumber.datatable.DataTableType;
import org.apiguardian.api.API;

@API(status = API.Status.STABLE)
public interface DataTableTypeDefinition {

DataTableType dataTableType();

/**
* The source line where the data table type is defined.
* Example: com/example/app/Cucumber.test():42
*
* @param detail true if extra detailed location information should be included.
* @return The source line of the step definition.
*/
String getLocation(boolean detail);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.cucumber.core.backend;

import io.cucumber.datatable.TableCellByTypeTransformer;
import org.apiguardian.api.API;

@API(status = API.Status.STABLE)
public interface DefaultDataTableCellTransformerDefinition {

TableCellByTypeTransformer tableCellByTypeTransformer();

/**
* The source line where the default data table cell is defined.
* Example: com/example/app/Cucumber.test():42
*
* @param detail true if extra detailed location information should be included.
* @return The source line of the step definition.
*/
String getLocation(boolean detail);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.cucumber.core.backend;

import io.cucumber.datatable.TableEntryByTypeTransformer;
import org.apiguardian.api.API;

@API(status = API.Status.STABLE)
public interface DefaultDataTableEntryTransformerDefinition {

TableEntryByTypeTransformer tableEntryByTypeTransformer();

/**
* The source line where the default table entry transformer is defined.
* Example: com/example/app/Cucumber.test():42
*
* @param detail true if extra detailed location information should be included.
* @return The source line of the step definition.
*/
String getLocation(boolean detail);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.cucumber.core.backend;

import io.cucumber.cucumberexpressions.ParameterByTypeTransformer;
import org.apiguardian.api.API;

@API(status = API.Status.STABLE)
public interface DefaultParameterTransformerDefinition {

ParameterByTypeTransformer parameterByTypeTransformer();

/**
* The source line where the default parameter transformer is defined.
* Example: com/example/app/Cucumber.test():42
*
* @param detail true if extra detailed location information should be included.
* @return The source line of the step definition.
*/
String getLocation(boolean detail);
}

This file was deleted.

18 changes: 14 additions & 4 deletions core/src/main/java/io/cucumber/core/backend/Glue.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,24 @@
@API(status = API.Status.STABLE)
public interface Glue {

void addStepDefinition(StepDefinition stepDefinition) throws DuplicateStepDefinitionException;
void addStepDefinition(StepDefinition stepDefinition);

void addBeforeHook(HookDefinition hookDefinition);
void addBeforeHook(HookDefinition beforeHook);

void addAfterHook(HookDefinition hookDefinition);
void addAfterHook(HookDefinition afterHook);

void addBeforeStepHook(HookDefinition beforeStepHook);

void addAfterStepHook(HookDefinition hookDefinition);
void addAfterStepHook(HookDefinition afterStepHook);

void addParameterType(ParameterTypeDefinition parameterTypeDefinition);

void addDataTableType(DataTableTypeDefinition dataTableTypeDefinition);

void addDefaultParameterTransformer(DefaultParameterTransformerDefinition defaultParameterTransformer);

void addDefaultDataTableEntryTransformer(DefaultDataTableEntryTransformerDefinition defaultDataTableEntryTransformer);

void addDefaultDataTableCellTransformer(DefaultDataTableCellTransformerDefinition defaultDataTableCellTransformer);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.cucumber.core.backend;

import io.cucumber.cucumberexpressions.ParameterType;
import org.apiguardian.api.API;

@API(status = API.Status.EXPERIMENTAL)
public interface ParameterTypeDefinition {

ParameterType<?> parameterType();

/**
* The source line where the parameter type is defined.
* Example: com/example/app/Cucumber.test():42
*
* @param detail true if extra detailed location information should be included.
* @return The source line of the step definition.
*/
String getLocation(boolean detail);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public interface StepDefinition {

/**
* The source line where the step definition is defined.
* Example: foo/bar/Zap.brainfuck:42
* Example: com/example/app/Cucumber.test():42
*
* @param detail true if extra detailed location information should be included.
* @return The source line of the step definition.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/
public final class MethodFormat {
private static final Pattern METHOD_PATTERN = Pattern.compile("((?:static\\s|public\\s)+)([^\\s]*)\\s\\.?(.*)\\.([^\\(]*)\\(([^\\)]*)\\)(?: throws )?(.*)");
private static final Pattern PACKAGE_PATTERN = Pattern.compile("[^,]*\\.");
private static final Pattern PACKAGE_PATTERN = Pattern.compile("[^,<>]*\\.");
private final MessageFormat format;

public static final MethodFormat SHORT = new MethodFormat("%c.%m(%a)");
Expand Down
Loading

0 comments on commit ce0a6b8

Please sign in to comment.