jsGiven/jsGiven

View on GitHub
documentation/user-guide.adoc

Summary

Maintainability
Test Coverage
:source-highlighter: pygments
:icons: font
:nofooter:
:docinfo: shared,private

= JsGiven User Guide
:toc: left
:toclevels: 5

== Introduction

JsGiven is a JavaScript library that helps you to design a high-level, domain-specific language for writing BDD scenarios.
You still use your favorite javascript test runner, your favorite assertion library and mocking library for writing your test implementations, but you use JsGiven to write a readable abstraction layer on top of it.

It's a JavaScript port of http://jgiven.org[JGiven] (written in Java).
JsGiven keeps the JGiven philosophy, concepts and uses its html5 reporting tool.

== Installation

JsGiven is released on https://www.npmjs.com/[NPM] and can be installed with either NPM or Yarn.

JsGiven should usually be installed as a https://docs.npmjs.com/files/package.json#devdependencies[devDependency] as it's not directly contributing as project dependency.

----
$ yarn add -D js-given <1>
$ npm install --save-dev js-given <2>
----
<1> With https://yarnpkg.com[Yarn]
<2> With https://www.npmjs.com/[NPM]

== Requirements

JsGiven works with classic JS test runners (Jest, Jasmine, Mocha, Ava, Protractor).

JsGiven requires Node v6.x or more to run.

== Getting started

=== Importing JsGiven

JsGiven functions or classes are available as https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export[named exports] of the 'js-given' module.

[source, js, indent=0]
----
include::../examples/jest-es2015/sum.test.js[tags=ImportJsGiven]
----

=== Set up JsGiven

JsGiven needs to be setup in each test source file by calling a setup function.

[NOTE]
It is necessary to do this in each test source file as some tests frameworks (Jest and probably Ava) actually run each test source file in a worker process in order to get parallel execution of tests.

==== For Rspec inspired frameworks (Jest, Mocha, Jasmine, Protractor)

In frameworks inspired by RSpec, JsGiven must be setup by calling the setupForRspec() function.
This function takes the describe and the it function from the test framework.

[source, js, indent=0]
----
include::../examples/jest-es2015/sum.test.js[tags=setupForRspec]
----

==== For Ava framework

In https://github.com/avajs/ava[Ava], JsGiven must be setup by calling the setupForAva() function.
This function takes the test from Ava.

[source, js, indent=0]
----
include::../js-given/spec/demo.spec.js[tags=setupForAva]
----

=== Create a scenario group

First of all you create a scenario group by calling the scenarios() function.

[source, js, indent=0]
----
include::../examples/jest-es2015/sum.test.js[tags=scenariosCallFirstPart]
include::../examples/jest-es2015/sum.test.js[tags=scenariosCallSecondPart]
----

. The first parameter is the group name, it identifies your scenario within the report.
    - You can use a "namespace" or "java package" naming with dots to introduce a hierarchy that will be presented in the html5 report (eg: analytics.funnels.tickets_sales)
. The second parameter is the stage class you will create very soon.
. The last parameter is a function that takes an object containing the given(), when(), then() methods and returns the scenarios object.

=== Create a Stage class

You now have to create a "Stage" class that extends the Stage base class.

[source, js, indent=0]
----
include::../examples/jest-es2015/sum.test.js[tags=SumStage]
----

A stage class contains multiple step methods that can be called in a scenario and that will appear in the report.
Step methods are the heart of JsGiven. The test initialization, execution and assertions **must** be implemented within step methods.

Every non-static method of a stage class that returns the stage instance (**this**) can be used as a step method.

[WARNING]
.Step methods must return the **this** reference!
====
This way JsGiven knowns which methods should be included in the report. Internal private methods usually do not return this.

Since there are is no concept of private methods in JavaScript, this is a major difference from JGiven.
====

There are no further requirements.

[NOTE]
.In addition, a step method should be written in snake_case
====
This way JsGiven knows the correct casing of each word of the step.
====

=== Write your first scenario

Now you can write your first scenario

[source, js, indent=0]
----
include::../examples/jest-es2015/sum.test.js[tags=scenarios]
----

The scenario() methods takes two parameters :

. The first parameter is an object with the optional parameters (such as tags or scenario extended description) (Both are not implemeted yet).
. The second parameter is the scenario description function.

=== Execute your scenario

You can execute your scenario by running your test runner as you usually run the tests.

----
$ jest
 PASS  ./sum.test.js
  sum
    ✓ Two numbers can be added (9ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.7s, estimated 1s
Ran all test suites.
----

== Report generation

JsGiven produces internal reports in JSON, that are not meant to be presented to users.

JsGiven converts these internal reports to a fully usable JGiven report.

=== Setting up the test npm scripts

Due to the parallel nature of some test runners (Jest & Ava), there is no simple way to generate the reports after running the tests.

Therefore, you have to set up 3 npm scripts that will :

- Clean the internal reports before starting the tests.
- Run the tests with your usual test running and generate the report if the tests have failed
- Generate the html5 report after the tests have successfully run.

You have to include the following scripts in you *package.json* file:

[source, json]
----
"scripts": {
  "pretest": "jsgiven clean",
  "test": "your_test_command || jsgiven report --fail", <1>
  "posttest": "jsgiven report"
}
----
<1> Where *your_test_command* is your test runner command (mocha, jest, jasmine or another one).

The *jsgiven* command is a CLI command tool provided by the module.

== Step methods

=== Completely hide steps

Steps can be completely hidden from the report by using the @Hidden decorator.
This is sometimes useful if you need a technical method call within a scenario, which should not appear in the report.

For example:

[source, js, indent=4]
----
include::../examples/jest-es2015/hidden-steps.test.js[tags=HiddenStep]
----

[NOTE]
.It is useful to write hidden methods in CamelCase
====
This will make it immediately visible in the scenario that these methods will not appear in the report.
====

There is an alternative to decorators that you can use if you don’t want to use decorators.
See <<Hide steps without decorators>>

In order to use hidden step with decorators, you have to setup decorators: See <<Configuration to use decorators>>

==== Hide steps without decorators

You can also declare the step methods you want to hide by using Hidden.addHiddenStep() static method instead of using the decorator.

[source, js, indent=0]
----
include::../examples/jest-es2015/hidden-steps.test.js[tags=HiddenStepES5]
----

== Stages and state sharing

In the previous example you have included all step methods in the same class.
While this is a simple solution, it's often suitable to have several stage classes.

=== Create Given/When/Then Stage classes

JsGiven allows to use 3 Stage classes.

You can declare the classes in your scenario

[source, js, indent=0]
----
include::../examples/jest-es2015/sum-multiple-stages.test.js[tags=SumMultipleStagesScenarioDeclaration]
----

And use all the methods of each stage in the given(), when(), then() chains:

[source, js, indent=0]
----
include::../examples/jest-es2015/sum-multiple-stages.test.js[tags=SumMultipleStagesScenario]
----

=== Sharing state between stages

Very often it is necessary to share state between steps.
As long as the steps are implemented in the same Stage class you can just use the fields of the Stage class.
But what can you do if your steps are defined in different Stage classes ?

In this case you just define the same field in both Stage classes.

The recommended approach is to use a the special decorator @State.
Both fields also have to be annotated with the special decorator @State to tell JsGiven that this field will be used for state sharing between stages.

The values of these fields are shared between all stages that have the same field with the @Stage decoration.

[source, js, indent=0]
----
include::../examples/jest-es2015/sum-multiple-stages.test.js[tags=SumStages]
----

There is an alternative to decorators that you can use if you don't want to use decorators.
See <<Using state sharing without decorators>>

In order to use state sharing with decorators, you have to setup decorators: See <<Configuration to use decorators>>

==== Using state sharing without decorators

You can declare the state properties to be shared using the *State.addProperty()* static method.

[source, js, indent=0]
----
include::../examples/jest-es2015/sum-multiple-stages-without-decorators.test.js[tags=SumStagesWithoutDecorators]
----

Both fields in the different classes have to be marked as state properties with the *State.addProperty()* static method.

The values of these fields are shared between all stages that have the same state properties.

== Parameterized steps

Step methods can have parameters.
Parameters are formatted in reports by using the toString() method, applied to the arguments.

The formatted arguments are added to the end of the step description.

[source, js, indent=0]
----
given().the_ingredient( "flour" ); // Given the ingredient flour
given().multiple_arguments( 5, 6 ); // Given multiple arguments 5 6
----

=== Parameters within a sentence

To place parameters within a sentence instead the end of the sentence you can use the $ character.

[source, js, indent=0]
----
given().$_eggs( 5 );
----

In the generated report $ is replaced with the corresponding formatted parameter. So the generated report will look as follows:

[source, js, indent=0]
----
Given 5 eggs
----

If there are more parameters than $ characters, the remaining parameters are added to the end of the sentence.

If a $ should not be treated as a placeholder for a parameter, but printed verbatim, you can write $$, which will appear as a single $ in the report.

[source, js, indent=0]
----
given().$$_$( 5); // Given $ 5
----

=== Parameters formatting

Sometimes the default toString() representation of a parameter does not fit well into the report.
In these cases you have several possibilities:

- Change the toString() implementation.
This is often not possible or not desired, because it requires patching primitive types or the modification of production code.

- Provide a wrapper class for the parameter object that provides a different toString() method.
This is useful for parameter objects that you use very often, but it makes the scenario code a bit more complex.

- Change the formatting of the parameter by using special JSGiven annotations.
This can be used in all other cases and also to change the formatting of primitive types.

==== Writing your formatter

You can write your own formatter using the buildParameterFormatter() function:

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting.test.js[tags=CustomFormatter]
----

You can then provide your formatter implementation that will convert the parameter to a string to be presented in the report

==== Using formatters

You can use your formatter as a decorator that takes the parameter name.

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting.test.js[tags=UsingCustomerFormatter]
----

The decorator takes the parameter names (you can supply multiple parameter names as a rest parameter https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters)
)

*When used in a scenario*:

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting.test.js[tags=FormatterExample]
----

There is an alternative to decorators that you can use if you don't want to use decorators.
See <<Using formatters without decorators>>

In order to use formatters with decorators, you have to setup decorators: See <<Configuration to use decorators>>

==== Using formatters without decorators

Instead of using the decorator, you can call the formatParameter() static method.
This method takes three parameters:

. The stage class
. The step method name
. The parameter names (you can supply multiple parameter names as a rest parameter https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters)

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting-without-decorators.test.js[tags=UsingCustomerFormatterWithoutDecorators]
----

==== Parameterized formatters

Sometimes your formatter implementation may need a custom parameter that you will need to pass in each stage (a date format, a currency name ...)

With higher order functions (https://en.wikipedia.org/wiki/Higher-order_function), you can make your formatter accept parameters:
This way you will write a function that accepts parameters and will call buildParameterFormatter().

When using it, you first call your function, then call the decorator:

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting.parametrized.test.js[tags=CustomFormatterParametrized]
----

This technique works as well, without decorators :

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting.parametrized.without-decorators.test.js[tags=CustomFormatterParametrizedWithoutDecorators]
----

==== Providers formatters

JsGiven provides three default formatters:

===== Quoted

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting-default-formatters.test.js[tags=QuotedExample]
----

When invoked as:

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting-default-formatters.test.js[tags=QuotedExampleUse]
----

Then this will result in the report as:

----
Then the message "Hello World" is printed to the console
----

===== QuotedWith

QuotedWith is a generalization of the Quote formatter that allows you to choose your quote charater.

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting-default-formatters.test.js[tags=QuotedWithExample]
----

When invoked as:

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting-default-formatters.test.js[tags=QuotedWithExampleUse]
----

Then this will result in the report as:

----
Then the message 'Hello World' is printed to the console
----

===== NotFormatter

NotFormatter allows you to write english positive or negative sentence bases on boolean values

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting-default-formatters.test.js[tags=FormatterExample]
----

*When invoked with true*:

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting-default-formatters.test.js[tags=FormatterExampleUseTrue]
----

Then this will result in the report as:

----
Then the message is displayed to the user
----

*When invoked with false*:

[source, js, indent=0]
----
include::../examples/jest-es2015/parameter-formatting-default-formatters.test.js[tags=FormatterExampleUseFalse]
----

Then this will result in the report as:

----
Then the message is not displayed to the user
----

== Parameterized scenarios

JsGiven scenarios can be parameterized.
This is very useful for writing data-driven scenarios, where the scenarios itself are the same, but are executed with different example values.

As most JS test frameworks do not include build-in helpers for parameterized tests, JsGiven provides a simple API to include test data and handle the different cases execution.

Instead of providing a scenario method, you use the parametrized() function, which takes two arguments:

- An array of parameters tuples (an array of array containing parameter values)
- The scenario function that takes as many parameters as the tuple size

[source, js, indent=0]
----
include::../examples/jest-es2015/parameterized-scenarios.test.js[tags=ParametrizedScenario]
----

When the scenario is run, the three cases are executed :

----
$ jest
 PASS  ./parameterized-scenarios.test.js
  parametrized-scenarios
    ✓ Scenarios can be parametrized #1 (8ms)
    ✓ Scenarios can be parametrized #2 (1ms)
    ✓ Scenarios can be parametrized #3 (2ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.71s, estimated 1s
Ran all test suites matching "parameterized".
----

If you only have one parameter, you can use the parametrized1() function which accepts an array of single values instead of tuples:

[source, js, indent=0]
----
include::../examples/jest-es2015/parameterized-scenarios.test.js[tags=Parametrized1Scenario]
----

=== Usage with Flow or TypeScript

In order to ensure proper typing between the parameters and their uses in the scenario function, you should use the specialized parametrizedN() functions.

Depending on the number of parameters, you can use the specialized parameterizedN() function.

Here is an example of the parameterized2() function :

[source, js, indent=0]
----
include::../examples/jest-es2015-flow/parameterized-scenarios.test.js[tags=ParametrizedScenarioTyped]
----

This way, the type-checker will be able to detect type errors between the parameter values and their uses in the scenario & step methods.

== Asynchronous testing

JsGiven supports asynchronous testing.

Asynchronous scenarios in JsGiven are written the same way synchronous scenarios are written :

- Scenarios functions remain fully synchronous and are still calling step methods synchronously.
- Step methods must still return the *this* reference.

However when a step method must perform some asynchronous work, it has to call the *doAsync()* function.
This functions accepts a function that returns a promise (or an async function: as it's the same signature).

Further step methods can be synchronous or asynchronous there is no need to use doAsync() in further step methods.
JsGiven will continue the scenario execution and execute the further step methods once the asynchronous execution is done.

Example using async functions :

[source, js, indent=0]
----
include::../examples/jest-es2015/async.test.js[tags=AsyncStage]

include::../examples/jest-es2015/async.test.js[tags=AsyncScenario]
----

Example using promises :

[source, js, indent=0]
----
include::../examples/jest-es2015/async.test.js[tags=PromiseStage]

include::../examples/jest-es2015/async.test.js[tags=PromiseScenario]
----

=== Required configuration for asynchronous testing

JsGiven relies on promises.

==== With Babel

If you're writing async/await code, make sure it is properly transpiled or you are using Node 8.

You will need to have a promise implementation (a native or polyfilled one)
If you use Babel directly with your test runner, Babel already includes polyfills for missing implementations. If not ensure they are included.

==== With TypeScript

If you're writing async/await code, make sure it is properly transpiled by TypeScript.

Depending on the target, you may have to include the "es2015.promise" library in you tsconfig.json

[source, json, indent=4]
----
"lib": [
  "es5",
  "es2015.promise"
],
----

== Using JsGiven

=== Supported Test runners

JsGiven supports the following test runners :

- https://facebook.github.io/jest/[Jest]
- https://mochajs.org/[Mocha]
- https://jasmine.github.io/[Jasmine]
- https://github.com/avajs/ava[Ava]
- http://www.protractortest.org/#/[Protractor]

JsGiven is tested internally using those frameworks image:https://travis-ci.org/jsGiven/jsGiven.svg?branch=master["Build Status", link="https://travis-ci.org/jsGiven/jsGiven"]

=== Configuration to use decorators

All step-related advanced features (state-sharing, hiding steps, formatters ...) are best used with decorators.
In order to enable decorators, some configuration is required.

==== With Babel

In order to use decorators, you have to include the following babel transform plugins in your babel configuration.

- transform-decorators-legacy
- transform-class-properties

[source, json, indent=0]
----
include::../examples/jest-es2015/.babelrc[]
----


[NOTE]
.You are not forced to use this as your *.babelrc* production configuration
====
You can use the decorators configuration only in your test setup (See https://babeljs.io/docs/usage/babelrc/#env-option).
Read your test framework documentation to see how you can achieve this.
====

==== With TypeScript

In order to use state sharing with decorators, you have to enable the "experimentalDecorators" option in your tsconfig.json

[source, json, indent=4]
----
"experimentalDecorators": true
----

=== Type checkers

==== With Flow

JsGiven includes build-in support for the https://flow.org/[Flow] type checker.
You don't have to install any type definitions.

A working example using Flow is provided : https://github.com/jsGiven/jsGiven/tree/master/examples/jest-es2015-flow

JsGiven is internally written with Flow.

==== With TypeScript

JsGiven includes build-in TypeScript definitions.
You don't have to install any type definitions.

A working example is provided : https://github.com/jsGiven/jsGiven/tree/master/examples/jest-typescript

=== Using JSGiven in pure ES5

Js Given is usable with ES5.

You can import JsGiven using regular *require()* calls:

[source, js, indent=0]
----
include::../examples/mocha-es5/sum.test.js[tags=ImportJsGivenES5]
----

You can declare the stage classes using classical prototypal inheritance.

[source, js, indent=0]
----
include::../examples/mocha-es5/sum.test.js[tags=SumStageES5]
----

You use JSGiven almost like in ES6

[source, js, indent=0]
----
include::../examples/mocha-es5/sum.test.js[tags=scenariosES5]
----

You can find a https://github.com/jsGiven/jsGiven/tree/master/examples/mocha-es5[working example] based on Mocha.

=== Fully working examples

Some examples are committed on the https://github.com/jsGiven/jsGiven[JsGiven repository]

These examples are tested on each commit against the latest stable version and against the current code by the https://travis-ci.org/jsGiven/jsGiven[Travis CI] integration

- https://github.com/jsGiven/jsGiven/tree/master/examples/jest-es2015[Jest + ES2015]
- https://github.com/jsGiven/jsGiven/tree/master/examples/jest-es2015-flow[Jest + ES2015 + Flow]
- https://github.com/jsGiven/jsGiven/tree/master/examples/mocha-es5[Mocha + ES5]
- https://github.com/jsGiven/jsGiven/tree/master/examples/jest-typescript[Jest + TypeScript]
- https://github.com/jsGiven/jsGiven/tree/master/examples/jest-node-8[Jest with Node 8, with the minimal transpilation required]