Unit Testing in Agile Web Projects

Why It Is Important and How to Get Started

Janko Hofmann
Aperto Stories

--

If you are a front-end developer, you have probably heard about unit testing and that it’s a good thing. The 2016 survey “The State of Front-End Tooling” shows that 52% of all developers unit test their code — a 12% increase compared to the year before.

This article aims to give an overview of the concepts and benefits of unit testing, especially in the context of agile projects, providing you all necessary information to get started on your own.

While the examples in this article primarily refer to web front-end development, unit testing is vital for all kinds of software projects such as mobile and desktop apps, back-end systems, and frameworks and libraries. The general principles outlined in this article also apply to these kinds of projects.

What Are Unit Tests and How Do They Work?

Unit testing means that you test individual parts (units) of your software. You do that by defining results that your code should produce for certain input values and verify whether they occur.

A unit test is a function that is executed by a software called a test-runner. In this function, you provide four things:

  • a short explanation on what you are testing
  • a part of your software that you want to test (for ex. a function or a class)
  • test data to supply as input or parameter to your code
  • a result that you expect your code to produce

You then execute the part of your software with your input data and compare the result with your expected result. If both are equal, the test passes. If they are not equal or other unexpected issues emerge before the comparison is executed (such as errors that are thrown), the test fails.

An actual test could look like this:

describe('hasSearchedBefore', () => {
it('should return true when search status is "success"', () => {
const state = { searchStatus: status.success };
expect(hasSearchedBefore(state)).to.be.true;
});
});

Here we are testing a function called “hasSearchedBefore” that takes a state object as argument and returns a boolean. We define our test function as a parameter to a function called “it”, which is provided by our test runner. The “expect”-function is part of our assertion library. An assertion library provides convenience methods to easily compare our actual result (hasSearchedBefore(state)) to our expected result (“true”).

Typically, many of these tests that have a similar purpose or test the same entity are grouped together to a test suite (the “describe”-function in our example). As for the “hasSearchedBefore”-function, we probably want to further test the function with a state that would produce a false return value and also with no parameters whatsoever.

Sometimes it happens that your code depends on external data or events and cannot be tested fully isolated. Since you want to restrict your test to the functionality of your unit and avoid side effects, you need to replace these external dependencies with static data so that this part of the program always behaves as expected. This static data is called mock data. There are several libraries available that can help you to mock certain functionalities of your software.

Why Would I Want to Write Unit Tests?

There are a lot of reasons why it is useful to write unit tests, the most obvious being:

  • you get a working documentation of your code for free
  • you can find and fix bugs before they occur in the QA process or at the end user
  • you automatically think about edge cases in your code more often
  • you get to produce better code that is more modular, dependency-free and reusable
  • you gain confidence in your code that enables you to refactor without worries
  • it gets tremendously easier to work together with other developers or to hand over the project to someone else
  • you get to sleep better at night ;)
  • it makes you a better developer
  • and most importantly: you don’t want to mess with this guy:
(Source: http://www.quickmeme.com/meme/359jzz)

What Should Be Tested?

  • Reusable components
  • Libraries, open source software in general
  • API calls and processing of responses in your app
  • Error Handling
  • App- and business logic
  • Views
  • Regression tests for bugs

How Do I Get Started with Unit Testing?

Step 1: Write Testable Code

The units in your code that you typically want to test are functions (including object methods and constructors). Your functions should be pure in a sense that they produce no side effects and only operate on the parameters they are provided. Naturally, a function that is easy to test has as few parameters as possible (a single parameter in the best case). A function with no parameters at all however cannot be pure. With every additional parameter your function depends on, the test effort increases exponentially because you would need to test all kinds of combinations of these parameters. If you encounter a lot of these functions in your app, you might want to refactor them into multiple smaller, single-purpose functions that can be tested individually.

Your code should be free of external dependencies wherever possible, otherwise you would have to mock everything that your code depends on, which can quickly result in a lot of effort that you want to avoid. Considering this fact, you will automatically produce modular code with lean interfaces that is not tightly coupled to other features and accepts potential dependencies as parameters.

In the best case scenario, your code is modular on a file basis so that every file with JavaScript code can be accompanied with an according test file. Assuming we have a file called personModel.js in our project, we would place another file called personModel.spec.js right next to it that contains all unit test for the person model.

Step 2: Set up the Tools to Test Your Code

There are a lot of different tools available for unit testing in web projects, many of which have the same purpose. If you are just getting started on the subject, this can easily be overwhelming, but actually you don’t need much to get going. We use the following test stack:

  • Node.js as test environment (you can also run tests in the browser, however this is harder to automate)
  • Mocha as test runner
  • Chai as assertion library

Apart from that, there are a handful of libraries that provide additional comfort or save you some work in certain situations. These tools have proven especially useful to us:

  • JSDOM to test DOM interaction (since Node.js has no DOM implementation)
  • Enzyme to easily test React components
  • Sinon to test function calls and mock asynchronous behavior and Ajax calls

Normally, you don’t need all of them, that largely depends on the application you are developing. There is no need to set up all these tools by hand, since there are a lot of different web project boilerplates for every occasion that you can use for your project. If you already have an internal build process that you want to keep, you can just extract the test functionalities from an existing boilerplate and copy it into yours.

Our front-end boilerplate from Aperto Move with the above-mentioned test stack can be found on GitHub:

When using our boilerplate, every file with the ending *.spec.js is automatically recognized as a file containing unit test, bundled together with all other unit test files and executed by Mocha.

In your test file, you would import the test libraries you need and the code you want to test via ES6 imports, then write your tests and execute the test task (“npm test”) on the command line.

All tests passing makes a happy developer

Bonus: If you are using a continuous integration environment such as Jenkins, Travis CI, Bamboo or the like, you can automatically execute this task as a part of your build step. This way, you can ensure that only code which passes all unit tests get deployed to your live server.

When Should I Test? Before or After the Implementation?

There are a lot of different opinions on whether to write unit tests before or after the implementation of the code you want to test. Both approaches have pros and cons:

When using Test Driven Development (TDD, meaning you write the test before the implementation), you are likely to develop your code more systematically and define your public interface thoroughly before writing the actual code.

However, this contradicts the agile principle to create tangible results based on working code as early as possible. In our Scrum team, we largely build our projects upon iterative, interdisciplinary feedback instead of creating a detailed plan of the solution upfront. Therefore we want to create working software as early as possible in order to get feedback quickly. This means trying different solutions where applicable and modifying the prototypical implementation based on early feedback. So in our case, it makes sense to write the tests not before the team has agreed upon the solution it wants to deliver. Otherwise, we would need longer to show something to get feedback and would possibly create tests for solutions that don’t make it into the final product.

So, in order to summarize: The ideal moment to write your unit tests largely depends on your project setup. If the solution you develop is very clear before you start coding, it is preferable to write your tests first. If your development process is heavily feedback-based, you might want to write your tests when you can be sure that your implementation does not change substantially afterwards.

How Does Testing Fit into the Agile Development Process?

In order to ensure that writing tests is not omitted, we create a mandatory subtask called “write unit tests” for every user story at the beginning of each sprint. This task will usually be finished after the internal UX- and UI review of the respective story, which is conducted right after the implementation.

In a sprint planning meeting, it is important to consider the additional time needed to write unit tests while estimating each user story. Note that if you are just getting started with writing unit tests in general, your velocity will be lower for your first few weeks, but it will soon return to normal. Ultimately, writing unit tests is an essential prerequisite to keep your velocity constant or even improve it for longer software projects with several developers.

Since product requirements can and will change more than once during the course of an agile software project, you need to refactor your code at times to be able to react to the new requirements. When your code base is tested thoroughly, you can easily do this without having to worry to break something. This is especially useful when multiple developers work on the same project. By looking at the unit test suite they can quickly understand what the code does and get notified immediately when a test fails after they have changed something.

Is It Worth the Effort Overall?

It is. The time it takes to write the tests will be made up at several other steps during the development process:

  • The QA engineer can test faster and has to write less bug reports
  • Less time is spent with fixing bugs which enables you to implement new features faster
  • Other developers don’t need to fear to break things and can dive into the project more quickly
  • If bugs do occur, they can be traced and located in less time
  • Regressions are eliminated if a test is written for every bug that is found

When Can I Omit Unit Tests?

In general, it is always a good idea to test your software. However, there are certain kinds of projects where unit testing is not practical. If you develop a rather simple software prototype as a showcase that is guaranteed not to be developed further into the final product, it might be unnecessary to write unit tests. Therefore, it can be left out to keep your development process lean and expedient.

What Else Can I Do?

Your testing process can be enhanced further when you have established a solid foundation of unit tests. One possible measure is to determine and continuously monitor your test coverage, which is a percentage of your application code that is executed in your unit test suite. This can be achieved with tools like Istanbul. However, the code coverage value should not be taken too seriously, because this value alone doesn’t tell anything about the quality and usefulness of your tests.

If browser compatibility is an important factor in your project, you can run unit tests automatically in different browsers on real devices either on your own device farm (for ex. using Karma) or using a cloud service such as Sauce Labs.

A useful addition to unit tests in larger software projects are integration tests. They are similar in their structure to unit tests, but serve a different purpose: While unit tests ensure that every part of your software works for itself, integration tests ensure that the different parts of your software work together flawlessly, meaning one module of your software can use the API of another one.

The last step in test automation are end-to-end tests. These tests cover certain processes that users perform with your application in their entirety, including business logic, front-end views and back-end interaction. In end-to-end tests, a series of user interactions is simulated with a script that controls an instance of your application in a browser and checks if your application responds to the input as expected. An example would be to automatically fill a login form with invalid login data and submitting it, then programmatically checking if an appropriate error message appears on the web site. A good Node.js solution for end-to-end testing that we use is nightwatch.js.

How Do I Get Started Now?

Simply start using tests when you begin your next project. Implementing tests afterwards for an existing project will be harder and may likely cause a lot of refactoring effort, since you probably haven’t written testable code without having testability in mind in the first place.

In the beginning this might take a while, but you will get quicker very soon. Also, your first tests won’t have the most elegant code, but that’s fine. An unsophisticated test with less-than-optimal performance is better than no test. Also, you will gain experience very quickly. Unit testing might look a little scary at first, but it’s not rocket science ;)

Aperto Move — An IBM Company is a Berlin agency for digital and mobile services with around 40 employees. How about you?

Follow us: apertomove.com / Facebook / Twitter

--

--

Janko Hofmann
Aperto Stories

Berlin-based freelance web engineer, software architect and consultant