Testing is essential to development. I've obviously used testing libraries such as Mocha, Chai, Ava, Tape, etc. But how would you go about writing one yourself? Especially how would you build a test framework using that same framework to test itself? I got to thinking, just what would a test runner or 'test harness' consist of? I thought the basics would be:
- It should be able to look for test file/s and execute tests within
- It should be able to make assertions within an executed test
- It should be able to report successes and failures of these assertions
So we would have a command line tool. We would run and record tests and their assertions and record results into a report that is delivered as the conclusion of the tests.
So I went ahead and created a testing framework called Testu, and this is what I learned.
Harness
The term harness, or 'test framework', is the thing that keeps track of the tests and sends the result to the reporter.
Assertions
Assertions are statements of facts or beliefs. So they are binary; that an assertion is either correct or wrong. There are many styles of assertions, some prefer a string like method such as expect(value).to.be.ok()
or simply assert(condition)
.
General assertions:
- 'assert', 'ok', 'isOk': truthy
- 'assertNot', 'not', 'notOk': falsy
- 'throws', 'throw': expect an exception to be thrown
- 'doesNotThrow', 'notThrow': will not throw an exception
- 'equal', 'equals', 'isEqual', 'is', 'strictEqual', 'strictEquals', 'strictIs', 'isStrict', 'isStrictly': equality condition
- 'deepEqual', 'deepEquals', 'isDeepEqual', 'isDeep', 'strictDeepEqual', 'strictDeepEquals': object/array equality condition
- 'notEqual', 'inequal', 'notEqual', 'notEquals', 'notStrictEqual', 'notStrictEquals', 'isNotEqual', 'isNot', 'doesNotEqual', 'isInequal': object/array not equal
- 'notNull', 'isNotNull': specific type assertion
Helper assertions:
- 'isAbove', 'above', 'greaterThan', 'over': numeric value above
- 'isBelow', 'below', 'lessThan': numeric value below
- 'isAtLeast', 'atLeast', 'greaterThanOrEqual': numeric value less than or equal
- 'isFalse': falsy value
- 'isTrue': truthy value
Included on the assert function is 'expect', so you can destructure and use the expect format:
test('using expect' ({ expect }) => {
expect(true).to.be.ok();
expect(0).to.equal(0);
});
- 'equal': equality test
- 'ok': truthy test
Reporter
I decided to use TAP (test anything protocol) for the default reporter.
Mocks and Stubs
I haven't included any of these in Testu (yet). However they are important enough to be covered.
- Stub
- A function that returns known dummy data
- Spy
- A stub with meta data such as what were the arguments or how many times the function was called
- Mock Objects
- simulate behaviour with simple dummy objects
- Fixtures
- dummy data, could be DOM, JSON, set up and torn down
- xvfb
- x virtual frame buffer
In computer science, test stubs are programs that simulate the behaviors of software components (or modules) that a module undergoing tests depends on Wikipedia
Things like Superagent can be used for integration testing your app. Ideal when using things like Mockgoose, a Mongoose mock etc.
How To Write A Unit Test
People bang on about unit tests, and quite right too. However, nobody really shows you how to write them. Like I say, devs bang on about 'Oh do you know how to unit test? I do and I'm great'. Yet they never show you. Same as tutorials, straight into coding solutions. To begin with, a unit test should be ...
- Relevant
- Repeatable
- Fast
- Isolated (test one thing only)
Fail, pass, refactor
The process is fail, pass, refactor. The first test you want to write should fail, so the first thing I like to do is just test that there is a function (if it is a function you are testing that is).
const sort = require('sort');
test('sort', assert => {
assert(typeof sort === 'function', 'should be a function');
});
As the tests get more specific, the code gets more generic
Uncle Bob can explain this a lot better than me :-)
Code coverage
A great tool is code coverage. It comes bundled with test frameworks such as Jest, or you can install istanbul or nyc code coverage tools. 100% coverage is not essential.