To get the ball rolling in JavaScript, we are going to write a Simple Calculator class. Our calculator only has the requirement to add or subtract a single set of numbers. Much of the code you write in TDD will start very simply, just like this example:
import { expect } from 'chai'
class SimpleCalc {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
}
describe('Simple Calculator', () => {
"use strict";
it('exists', () => {
// arrange
// act
// assert
expect(SimpleCalc).to.exist;
});
describe('add function', () => {
it('exists', () => {
// arrange
let calc;
// act
calc = new SimpleCalc();
// assert
expect(calc.add).to.exist;
});
it('adds two numbers', () => {
// arrange
let calc = new SimpleCalc();
// act
let result = calc.add(1, 2);
// assert
expect(result).to.equal(3);
});
});
describe('subtract function', () => {
it('exists', () => {
// arrange
let calc;
// act
calc = new SimpleCalc();
// assert
expect(calc.subtract).to.exist;
});
it('subtracts two numbers', () => {
// arrange
let calc = new SimpleCalc();
// act
let result = calc.subtract(3, 2);
// assert
expect(result).to.equal(1);
});
});
});
If the preceding code doesn't make sense right now, don't worry; this is only intended to be a quick example of some working test code. The testing framework used here is Mocha, and the assertion library used is chai. In the JavaScript community, most testing frameworks are built with BDD in mind. Each described in the code sample above represents a scenario or a higher-level requirements abstraction; whereas, each it represents a specific test. Within the tests, the only required element is the expect, without which the test will not deliver a valuable result.
Continuing this example, say that we receive a requirement that the add and subtract methods must be allowed to chain. How would we tackle that requirement? There are many ways, but in this case, I think I would like to do a quick redesign and then add some new tests. First, we will do the redesign, again driven by tests.
By placing only on a describe or a test, we can isolate that describe/test. In this case, we want to isolate our add tests and begin making our change here:
it.only('adds two numbers', () => {
// arrange
let calc = new SimpleCalc(1);
// act
let result = calc.add(2).result;
// assert
expect(result).to.equal(3);
});
Previously, we have changed the test to use a constructor that takes a number. We have also reduced the number of parameters of the add function to a single parameter. Lastly, we have added a result value that must be used to evaluate the result of adding.
The test will fail because it does not use the same interface as the class, so now we must make a change to the class:
class SimpleCalc {
constructor(value) {
this._startingPoint = value || 0;
}
add(value) {
return new SimpleCalc(this._startingPoint + value);
}
...
get result() {
return this._startingPoint;
}
}
This change should cause our test to pass. Now, it's time to make a similar change for the subtract method. First, remove the only that was placed in the previous example:
it('subtracts two numbers', () => {
// arrange
let calc = new SimpleCalc(3);
// act
let result = calc.subtract(2).result;
// assert
expect(result).to.equal(1);
});
Now for the appropriate change in the class:
subtract(value) {
return new SimpleCalc(this._startingPoint – value);
}
Out tests now pass again. The next thing we should do is create a test that verifies everything works together. We will leave this test up to you as an exercise, should you want to attempt it.