Unit Test Structure in Karma Unit Tests

After having written well over a thousand unit tests in Karma, I’ve identified what I think to be the proper way to structure unit tests in the framework.

Testing In Isolation

Before we dig into how to test in Karma, there is a concept that needs to be discussed prior to the specific framework. That is the concept of testing in isolation. The goal of this is to ensure that your unit tests are testing only the code you are intending to test. This means mocking the dependencies that you’ve written (one could argue to also mock third party dependencies such as momentjs). For instance, if you have a helper function that should upper case a string, then mock it to always return an uppercase string.

That way, if your dependency ends up breaking, its unit tests will fail until it’s fixed, but not give a false flag on the unit tests that depend on it. If you are not testing in isolation, then it is not a unit test.

This also means ensuring that your test data and dependencies are not being manipulated by other tests. For instance, if you mock an instance of a function that is stored globally in your application, and then create a second test file expecting that object to be in its original state, it would fail.

One way to highlight the problem is to use fdescribe to execute a single describe block of tests. If it works while using fdescribe but fails when not executed in isolation, then there is test bleed.

describe and it statements

Karma’s unit testing framework allows for the tests to read as sentences very, very cleanly. In addition, the better your describe and it statements are written, the easier it is to read any errors that may occur during a unit test.

First, your describe blocks should describe the high level summary of a test suite. These describe blocks can be nested, which leads to even more power in describing what tests are running. For instance, let’s take an Angular controller test suite:

In the event of a failure on the test above, it would be proceeded with MyController .$onInit in the error statement. My personal preference is to keep the describe blocks fairly limited in their wording. Typically, I'll do function calls, then have a nested describe block describing the particular use case, as such:

You shouldn’t have to go much further than this with nested describe blocks.

The it blocks should be pretty straightforward in their description. They’re typically started with “should” and describe the expected result for a given test criteria. For example:

Using beforeEach to properly set up test data

An awesome feature of Karma is the beforeEach functionality in a describe block. This executes before each test is executed in the order in which they are described in the nested describe blocks of a given unit test.

This is an extremely useful behavior to take advantage of. It allows the test writer to set up global state of an application, then specifically a given test suite.

How I typically approach this is to use the outer most describe block to construct the necessary objects (such as a controller, service, etc). The most deeply nested beforeEach is used to set up the proper data for the test cases.

Using afterEach to reset test state

Assuming you’ve modified the state in any of your tests, restoring that original state back to the objects will save you a headache. For instance, if you mock a function on an object to return a specific value, in your afterEach on the same describe block of your mocking beforeEach, you should negate that mock and return it to the original functionality. This is how to best avoid test bleed.

In addition, I always declare the afterEach immediately following the beforeEach. This way, I’m not having to scroll through hundreds of lines of tests to identify what is occurring after the execution of each test. In addition, Karma doesn’t care where it is declared in the block scope, so even more reason to move it up!

Front End Developer