Test Driven JavaScript

What is TDD/BDD development?

TDD comes from “Test Driven Development”. OK, but what does “doing TDD” mean? Doing TDD means letting the tests drive your design, hence the “Driven” part in the name. What that boils down to is writing your tests first, watching them fail and then making them pass by writing your code.

Yes, this can sound pretty confusing! But trust me, it’s worth it. Read on to rest of the post, where I present, a step by step process of writing code using TDD.

FYI: There is a derivative of TDD called BDD “Behavioural Driven Development”. Basically, it’s the same thing as TDD: you write tests for your code first, but the difference consists in how these tests are written. BDD describes the tests using the Gherkin language (a known tool for this is Cucumber).

Note: The examples going forward are written in JavaScript and I will present some specific tools and some web examples. Don’t worry about it, if you know some programming then you are good to go. The concepts presented here can apply to any language and you can easily find equivalent tools.

Test Types

When you write code you must know if it runs well. This means you must know if there are bugs or logic errors. You achieve this by executing the code against an input.

Let’s say we have the following code:

TDD Sample Code

The succ function receives an initial number (start)  and will return an array with its successors. The number of returned successors is limited by the second parameter, the range, if it was not limited we would have had an infinite loop.

After we have the code we want to check that it really returns the successors of the received number. I guess everyone does this after finishing writing the code.

TDD Check successors

The easiest way to do this is by calling the function and then printing out the result.

This type of testing is called integration testing. The integration tests are test case scenarios that exercise that multiple parts of your app are working correctly as a whole. Usually, integration tests are written to test the interoperability of two distinct systems. For example, in web development; your app is fetching data from the database, you also test your app and the database too. Practically, we are testing that both entities are communicating well.

Usually programmers prefer another type of testing which is called unit testing. Unit tests decouple your application in small parts and each part is tested independently from the others. In our example the two pieces are obviously the succ and the add functions. Each one must be tested separately.

TDD Unit Test

First we test the add function and only then do we test the succ function, that internally uses the add function, because if the add functions doesn’t run correctly neither the succ function will run correctly. This type of tests are poor unit tests, because they don’t isolate all the pieces, that’s why it’s optimal to mock things, but we will speak about that later.

TDD Function

The code presented so far may be only a tiny part of a really big project, and this is only a small feature. How do we test an entire application flow?

Our app does more stuff with the received number, besides computing the perfect squares.

The unit and integration tests can guarantee that our code performs as designed, but now we want to check that our whole app is functioning well.

We can achieve this through functional testing, also called end to end testing (E2E). Functional tests test your entire app, from the beginning to the end. In web development functional testing means a way to automate a browser instance, in which we can simulate a real user interaction, like clicking around, etc. A tool for this is Selenium.

Unit Integration Functional
Purpose prevents bugs or logic errors checks interoperability between parts validates complete functionality
Scope individual units a cluster of units the whole app
Quantity many fewer than unit fewer than integration
Speed fast slow slow motion
Pro
  • easy to write and maintain
  • helps to write better structured code
  • guarantees that your app parts will work together
  • completion for unit tests
  • validates the entire app flow
  • tests the user expectations
Cons
  • don’t guarantee that your app will work
  • not so easy to maintain, usually needs some setup
  • very hard to maintain due to high complexity

Assertions

An assertion is the way to validate that a test has passed or failed. Let’s take an example:

TDD Check successors

When we run the succ function against the given input we have the expectation that the result will always be: 11, 12, 13, 14, 15.

However, inspecting things by hand isn’t a feasible option, especially since the code might change sometime in the future. We must automate this in some way. How? We do an assertion:

TDD Assertion

This is saying: “I expect that the result of running the succ function against 10 and 5 will be equal to the above array”. This assertion is equivalent to:

TDD Assertion equivalent Loop

If it finds that an element is not equal, it will notify us through an Error, which means that the test has failed, else it does nothing and that means that the test has passed. This is exactly the behaviour of assertion libraries, a well known one being Chai. We need this type of libraries because it’s much easier and comfortable to do one-liner assertions than to write the same code every time, like above.

Test runners

We need to separate our testing code from our actual code. Now how will you structure your tests? Will you call every function and then expect something? Sounds good, but if one test fails the others will not have the chance to get executed. Another disadvantage is if you assert in more than one test, things to be equal and one test will fail, you will get as output only “Not equal”, accordingly to the code in the above section, how will you know which test has failed?

We need another solution, which is a test runner. We will use Mocha.

TDD Test Runner

This is a test, there are two possible cases:

  • the assertion fails, i.e. throws an Error, which the test runner catches and will mark the test as failed,;
  • the assertion passes, i.e. it doesn’t throw any Error and won’t return anything. The test runner will notify us that the test has passed.
Of course tests can be grouped and many other things, but I think you got the main idea.

Mocking

I guess you may have already heard of this, because it’s a common practice in the unit testing process. If you remember the unit test for the succ function, you saw that it is a poor unit test, because if the add function is broken the succ function will fail too. So we must replace the add function, but only in the unit test for the succ function.

The best explanation is an example (please read the comments):

TDD Mocking Example

I have used more complex JavaScript in this example, to override the add function, don’t bother with it. Now our add function returns on first call 11, on the second 12 and so on. We are controlling its behaviour, because we know what it must return, this way we are isolating our succ function. This is mocking.

You will think what a mess, so much code to write, but of course there are mocking libraries. I will present to you Sinon.

TDD Mocking Library

Nice! It’s totally equivalent with the precedent snippet.

Summing up

I know that sometimes it’s hard to write tests and you may not be willingly doing this, but believe me it’s worth it and it can relieve you from horrible bugs and painful debugging. I hope you found my explanations helpful and will consider writing tests for your code from now on. I can tell you from my co-op experience at Hootsuite I’m a happier programmer because of it.

Florentin Simion

 

About the Author

Simion Florentine is a Co-op on the Analytics UI team. He loves anything that is related to programming or technology, especially web development. In his free time he likes to party hard! Follow him on Twitter @flore7Seven and Github @flore77