For: developers, architects and teamleads that want to incorporate unit-testing for their TypeScript projects
A couple of blog posts ago we’ve set up a basic build-line, in particular for TypeScript. In this post we’ll get our hands-on again and apply some automagic stuff for doing BDD and / or behavioral-testing on our builds.
note: this post only deals with the ‘how‘, not the ‘why‘ and ‘when‘. Read this if this has your interest.
Setting up the environment for Behavioral Testing
Lets start with setting up a testsuite.
We usually need stuff like
- something that connects to a browser
- something that runs the tests
- something that interprets the tests
- something that compiles all needed files
There is this wonderful package called chimpjs that already helps us out on most of these facets. It does so by integrating and sprinkling magic over the following tools:
- Mocha, Jasmine or Cucumber.js
- Selenium and WebdriverIO
- Chai or Jasmine assertion libraries inside your steps
- Built in Node.js, works for any web application (with special Meteor support)
Let’s install it and see from there.
╭─tim@The-Incredible-Machine ~/Git/build-process ‹BDD› ╰─➤ npm i chimp ts-node --save-dev ╭─tim@The-Incredible-Machine ~/Git/build-process ‹BDD*› ╰─➤ ./node_modules/.bin/typings i cucumber chai --save-dev --source=dt
Configuring Chimp
Let’s set up chimp. Chimp is primarily a wrapper and seamless integration of multiple test frameworks, so it might not come as a surprise that we can set config options to these individual frameworks. By default the configuration options will be as such:
https://github.com/xolvio/chimp/blob/master/src/bin/default.js
These options can be overridden in our own file, and we have to, because chimp by default isn’t set up to use TypeScript.
Create a file chimp.conf.js.
module.exports = { // - - - - CUCUMBER - - - - path: './feature', compiler: 'ts:ts-node/register' };
Extending the Makefile
Add your test-routine to the makefile
.PHONY: [what was already in there] test bdd
and add the rules (extend test if you’ve also done the tdd post):
test: node_modules/.bin/chimp chimp.conf.js --chrome bdd: node_modules/.bin/chimp chimp.conf.js --watch
Let’s also create the proper directories
╭─tim@The-Incredible-Machine ~/Git/build-process ‹BDD*› ╰─➤ mkdir -p feature/step_definitions
Create some tests
In order for us to know if we’ve properly set up the test framework, we want to create some tests. Since we’ve already created some nonsense during the creation of the generic build process, we’ll continue on that.
First create the .feature file
The feature file should tell in plain English what feature we expect, and how it behaves in different scenarios.
in: features/config.feature
@watch @feature Feature: Seeing the effect of the config on the screen In order to know if the config was correctly applied, As a Developer I want to test some of the aspects of the config on the screen Scenario: Check if background color is corect Given the config has the color set to blue When we look at the website Then I should be having a blue background
Then we write implementation for this feature
The featuretest as written, can not directly be interpreted by our test framework. Our script just doesn’t know what ‘background color’ means, and what element is been intended to check. So that’s why we create support for these steps. The nice thing is, that you might notice some punch holes in the sentences. Like ‘blue’, might be switched for another color, and ‘background’ might be ‘font-color’ or something along these lines. If you cleverly analyse your scenarios, you might become able to recognise standard patterns that you can re-use.
Be careful! A common caveat is that you start writing a language processor. Don’t do it! Tests should:
- be straightforward
- be easy to understand
- have no deep connections with other tests
Here’s the example implementation of the feature scenario. Put it in feature/step_definitions/config.ts
/// <reference path="../../typings_local/require.d.ts" /> import IConfig from "../../ts/i_config" let config = <IConfig>require("../../conf/app.json"); export default function() { this.Given(/^the config has the color set to ([^\s]+)$/, function (color: string) { if (config.color !== color) { throw "Color in config mismatches the test scenario"; } }); this.When(/^we look at the website$/, function () { this.browser.url('http://localhost:9080'); return this.browser.waitForExist('body', 5000); }); this.Then(/^I should be having a ([^\s]+) background$/, function (color: string) { let browserResponse = this.browser.executeAsync(function(color: string , done: (response: boolean) => void) { let compareElem = document.createElement("div"); compareElem.style.backgroundColor = color; document.body.appendChild(compareElem); let bodyElem = document.querySelector('body'); done( window.getComputedStyle(compareElem).backgroundColor == window.getComputedStyle(bodyElem).backgroundColor ); }, color); if (!browserResponse.value) { throw "BackgroundColor didn't match!"; } }); }
Running the tests
By now we have set up TDD and BDD tests with TypeScript. A simple
╭─tim@The-Incredible-Machine ~/Git/build-process ‹BDD*› ╰─➤ make test
Should give you something like this:
Conclusion and notes
We are now fully able to write our tests – feature as well as function – in TypeScript, and have integrated them in our example build-process. We can run these tests on our own machine to verify our project locally. BDD and TDD are set up separately so that we have more grip on either of the testing solutions and prevent coupling where not needed.
We are however not completely done yet.
- We will have to set up some CI / CD make-tasks that can be ran at a headless server since we now leverage the browser in our own OS.
- We will need make sure our watchers and compilers are set up properly, in order for BDD and TDD to run nicely in the back while developing our code.
We will go more in-depth on those aspects when we start hooking our project up to nginx and really start developing an application.
Changes applied in this blog post can be found at github.
Suggestions, comments or requests for topics? Please let me know what you think and leave a comment or contact me directly.