26
Test Driven Development With TypeScript
Nobody ever liked writing test, when i left school i thought that i had escaped writing test. Enter software development and i realize i still can't run away from tests. Why do we even have to write tests? Tests are important in the software development cycle because it bears witness to these thing;
- Your understanding of the problem you are trying to solve, writing tests will prove that you understand the problem and the software requirements.
- That your solution is actually doing what you built it to do? How else can you prove that? We can run it in the browser to check the result of our code but that's only good for simple projects where you have a lot of time, and if you are mostly concerned with the look and design.
- It is a really good form of documentation.
When many people hear about TDD, they tend to cringe, and think that is a very difficult and complicated process. However you will find out that that's not always the case. Test's are actually simple to write that is if you understand how you code implementation works.
This does not mean that i write tests for all my projects, I only write tests when i feel the logic of the code is quite complicated and it wasn't too long ago i started writing tests. Since then i would tell you honestly i have seen that the benefits of writing tests outweighs any possible drawbacks you can think of. In this article we will meddle into test driven development.
What is TDD? TDD simply put is a software development principle that places emphasis on writing unit and integration test on our software to test it's functionality and/or behavior. Irrespective of the language you are currently working with, there are various means that you can employ to test your code, since nodejs is a superhero platform, we will be testing some JavaScript code. Most tests fanatics believe that you should write your test before you write your code? Who ever thought of that!! This actually makes sense because writing your test is like writing a software specification.
You have a problem, first you write the tests that fails, you then write code to pass the test, after which you re-factor your code to optimize it and that forms the TDD cycle.
We will just tackle a small problem. We have a user on a platform, they should be able to do the following;
- a user should have a profile.
- a user should be online when they login,
- they should be offline when they logout
First of all before we go into solving the problem, we need to setup a development environment that we can write our tests in. If you don't already have nodejs installed on your computer, go ahead and install the latest version of nodejs from the official website. Create a new node projects, run npm init --y
, this will generate a package json file that will track our dependencies.
We need to install the jasmine framework for nodejs, to that we run npm install jasmine
. After that you run jasmine init
this will create a spec
folder that will contain our tests and a support
folder that holds a jasmine.json
which is a configuration file for jasmine.
We can approach the problem in any manner we see fit, but personally i think a user should only have a profile when they login, otherwise they are offline and there is no profile. Jasmine test files end with the extension **.spec.js
. Test files should sit in the spec
folder.
// We will import a user class we will create later
import User from '../models/user'
// describe function defines a test block,
describe('just testing the user', () => {
// actual test are written in it functions
it('a new user should be offline', () => {
const sam = new User()
// expect something to happen
expect(sam.onlineStatus).toBe(false)
})
})
And that was how easy it was, if you strip away the comments it took us just 7 lines of code, and that's because we used proper indentation. In jasmine a describe function is a global function that is used to define a test suite. It takes a string and a function as arguments. Normally you use the string to give a hint about what you plan to do inside the function.
it
functions are the actual specs, they are also global jasmine functions they are quite similar to describe function in that, they accepts a string and a function as arguments. An it
function will contain a one or more expectations that test the state or behavior of our code, we can expect multiple things in a single it
function or use multiple it
functions, it is down to you to decide.
Jasmine is a Behaviour Driven Development tool, it tests the behaviour of our code by writing expectations for it. Expectations are the bedrock of the tests, it takes a value which you expect to be truthy or falsy, this is evaluated by chaining the expect
method with one of jasmine's built in matchers
. The value passed to the expect
function is called the actual
matchers are used to make a comparison between the actual
and the expected, which is passed to the matcher
. Above we expected Sam's online status to be false using the toBe()
matcher. This is one of jasmine's built in matchers and there are loads of matchers for almost all scenarios. If still don't find any matcher for your own test case, you can build one your self.
If we run the test above by hitting npx jasmine
it will fail because; we have not created the file for the user class, which we will proceed to create in the appropriate directory. On the root level of the project create a folder models/user.ts
next to the spec folder. You will have observed that i'm using typescript here but you can easily compile down to javascript.
// User
export default class User {
constructor(){},
onlineStatus = false
}
If you save this file and run your tests it should pass now. Let's write the second test case, we will be checking if the user is logged in and they have a profile. We will re-factor our tests because now we might be using more than one it block and we need a consistent reference to the user.
// We will import a user class we will create later
import User from '../models/user'
describe('just testing the user', () => {
let sam;
// beforeEach, is used to set a config before
// each of the spec runs
beforeEach(()=> {
sam = new User();
})
it('a new user should be offline', () => {
expect(sam.onlineStatus).toBe(false)
})
it('sam should login and have a profile', ()=> {
sam.login('[email protected]', 'password')
expect(sam.onlineStatus).toBe(true)
expect(sam.profile.email).toBe('[email protected]')
})
})
If we run the test again npx jasmine
we will see our second spec fail, this spec is called a failing spec. A spec with one or more expectations that are not truthy fall in this category, while the first test where all expectations are met is called a passing spec.
A new function was added inside the describe
block, beforeEach
this function is one of four setup and tear down functions in jasmine that we can use to configure or clean up after each spec, the name of each function says it all. The others are beforeEach, afterAll and beforeAll
. Let's modify the user class to implement these specifications we just defined above.
// User
export default class User {
constructor(){},
public profile
onlineStatus = false
login(email: string, password: string){
this.profile = {
email: email
}
return this.profile
}
}
I would like to believe the third case scenario should be quite easy for you to implement by now, i would like to see some suggestions... Hope you enjoyed it..
26