Home and Back

Driving in along that coastal plain, bleached stubble flowing to the cliff tops on the left, up over the diminutive hillocks to the right, a feeling of strangeness and unreality began to set in. Dry…

Smartphone

独家优惠奖金 100% 高达 1 BTC + 180 免费旋转




An Introduction to Jasmine Unit Testing

Jasmine is the most popular JS library for unit testing web apps. In this tutorial, designed for beginners, we’ll present you with a quick and complete guide to testing with Jasmine.

You’ll get introduced to Jasmine, a popular behavior-driven testing framework for JavaScript. We’ll also see a simple practical example on how to write unit tests with Jasmine which can help you easily check for bugs in your code.

In nutshell, we’ll see how to write test suites, specifications and expectations and how to apply built-in Jasmine matchers or build your own custom matchers

We’ll also see how you can group suites for the sake of organizing your tests for more complex code bases.

Jasmine has many features such as:

Jasmine is an open source tool that’s available under the permissive MIT license. As of this writing the latest major version is Jasmine 3.0 which provides new features and some breaking changes. The 2.99 release of Jasmine will provide different deprecation warnings for suites that have different behavior in version 3.0 which will make it easy for developers to migrate to the new version.

You can use Jasmine in many different ways:

Now let’s focus on how to use Jasmine with JavaScript:

Then simply extract the zip file, preferably inside a folder in the project you want to test.

The folder will contain a bunch of default files and folders:

/src: contains the source files that you want to test. This may be either deleted if your already have your project's folder setup or can also be used when appropriate for hosting your source code.

/lib: contains the core Jasmine files.

/spec: contains the tests that you are going to write.

SpecRunner.html: this file is used as a test runner. You run your specs by simply launching this file.

This is the content of a default SpecRunner.html file:

Remember that you need to change the files included from the /src and /spec folders to contain your actual source and test files.

You can also use Jasmine as a library in your project. For example the following code imports and executes Jasmine:

First we require/import Jasmine and we use the loadConfigFile() method to load the config file available from spec/support/jasmine.json path then finally we execute Jasmine.

You can also use Jasmine from the CLI which allows you to easily run Jasmine tests and by default output the results in the terminal.

We’ll follow this approach to run our example tests in this guide, so first go ahead and run the following command to install Jasmine globally:

Now, create a folder for your project and navigate inside it:

Next, run the following command to initialize your project for Jasmine:

This command simply creates a spec folder and a JSON configuration file. This is the output of the dir command:

This is the content of a default jasmine.json file:

If you don’t use the default location for the jasmine.json configuration file, you simply need to specify the custom location via the jasmine --config option.

In this section we’ll learn about the basic elements of Jasmine testing such as suites, specs, expectations, matchers and spies, etc.

In your project’s folder, run the following command to initialize a new Node module:

This will create a package.json file with default information:

Next, create an index.js file and add the following code:

A suite groups a set of specs or test cases. It’s used to test a specific behavior of the JavaScript code that’s usually encapsulated by an object/class or a function. It’s created using the Jasmine global function describe() that takes two parameters, the title of the test suite and a function that implements the actual code of the test suite.

Let’s start by creating our first test suite. Inside the spec folder create a MyJSUtilitiesSpec.js file and add:

MyJSUtilities is the name of this top-level test suite.

For better organizing and accurately describing our set of tests we can nest suites inside the top-level suite. For example, let’s add two suites to the MyJSUtilities suite:

Inside the the Math Utils suite, let’s also add two nested suites:

We are grouping related tests into tests for String Utils, Basic Math Utils and Advanced Math Utils and nesting them inside the top-level test suite MyJSUtilities. This will compose your specs as trees similar to a structure of folders.

The nesting structure will be shown on the report which makes it easy for you to find failing tests.

You can temporarily disable a suite using the xdescribe() function. It has the same signature (parameters) as a describe() function which means you can quickly disable your existing suites by simply adding an x to the function.

Specs within an xdescribe() function will be marked pending and not executed in the report.

A spec declares a test case that belongs to a test suite. This is done by calling the Jasmine global function it() which takes two parameters, the title of the spec (which describes the logic we want to test) and a function that implements the actual test case.

A spec may contain one or more expectations. Each expectation is simply an assertion that can return either true or false. For the spec to be passed, all expectations belonging to the spec have to be true otherwise the spec fails.

Inside our String Utils suite, add these specs:

Inside our Basic Math Utils suite let’s add some specs:

For the Advanced Math Utils, let’s add the specs:

Just like suites, you can also exclude individual specs using the xit() function which temporary disables the it() spec and marks the spec as pending.

Expectations are created using the expect() function that takes a value called the actual (this can be values, expressions, variables, functions or objects etc.). Expectations compose the spec and are used along with matcher functions (via chaining) to define what the developer expect from a specific unit of code to perform.

A matcher function compares between an actual value (passed to the expect() function it's chained with) and an expected value (directly passed as a parameter to the matcher) and returns either true or false which either passes or fails the spec.

You can chain the expect() function with multiple matchers. To negate/invert the boolean result of any matcher, you can use the not keyword before calling the matcher.

Let’s implement the specs of our example. For now we’ll use we’ll use expect() with the nothing() matcher which is part of the built-in matchers which we'll see a bit later. This will pass all specs since we are expecting nothing at this point.

This is a screenshot of the results at this point:

We have eight passed specs and zero failures.

You can either use built-in matchers or also create your own custom matchers for your specific needs.

Jasmine provides a rich set of built-in matchers. Let’s see some of the important ones:

Let’s now implement our specs with some of these matchers when appropriate. First import the functions we are testing in our MyJSUtilitiesSpec.js file:

Next, start with the String Utils suite and change expect().nothing() with the appropriate expectations.

For example for the first spec, we expect the toLowerCase() method to be first defined and secondly to return a lower case string i.e:

This is the full code for the suite:

For example, let’s take the following spec:

Let’s suppose that the isEven() method is not implemented. If we run the tests we'll get messages like the following screenshot:

The failure message we get says Expected undefined to be defined which gives us no clue of what’s happening. So let’s make this message more meaningful in the context of our code domain (this will be more useful for complex code bases). For this matter, let’s create a custom matcher.

We need to register the custom matcher before executing each spec using the beforeEach() method:

We can then use the custom matcher instead of expect(utils.isEven).toBeDefined():

This will give us a better failure message:

For initializing and cleaning your specs, Jasmine provides two global functions, beforeEach() and afterEach():

For example, if you need to use any variables in your test suite, you can simply declare them in the start of the describe() function and put any initialization or instantiation code inside a beforeEach() function. Finally, you can use the afterEach() function to reset the variables after each spec so you can have pure unit testing without the need to repeat initialization and cleanup code for each spec.

The beforeEach() function is also perfectly combined with many Jasmine APIs such as the addMatchers() method to create custom matchers or also with the done() function to wait for asynchronous operations before continue testing.

You can force a test to fail using the global fail() method available in Jasmine. For example:

You should get the following error:

When you are unit-testing your code, errors and exceptions maybe thrown, so you might need to test for these scenarios. Jasmine provides the toThrow() and toThrowError() matchers to test for when an exception is thrown or to test for a specific exception, respectively.

For example if we have a function that throws an TypeError exception:

You could write a spec that to test for if an exception is thrown:

Or you could also use test for the specific TypeError exception:

More often than not, methods depend on other methods. This means that when you are testing a method, you may also end up testing its dependencies. This is not recommended in testing i.e you need to make sure you test the pure function by isolating the method and seeing how it behaves given a set of inputs.

Jasmine provides two ways for spying on method calls: using the spyOn() or the createSpy() methods.

You can use spyOn() when the method already exists on the object, otherwise you need to use jasmine.createSpy() which returns a new function.

By default a spy will only report if a call was done without calling through the spied function (i.e the function will stop executing), but you can change the default behavior using these methods:

You can use a spy to gather run-time statistics on the spied function, for example if you want to know how many times your function was called.

Say we want to make sure our toUpperCase() method is making use of the built-in String.toUpperCase() method, we need to simply spy on String.toUpperCase() using:

The test has failed due to the second expectation because utils.toUpperCase("hello world") returned undefined instead of the expected HELLO WORLD. That's because, as we mentioned, earlier after creating the spy on toUpperCase(), the method is not executed. We need to change this default behavior by calling callThrough():

Now all expectations pass.

You can also use and.callFake() or and.returnValue() to fake either the spied on function or just the return value if you don't to call through the actual function:

Now, if we end up not using the built in String.toUpperCase() in our own utils.toUpperCase() implementation, we'll get these failures:

The two expectations expect(String.prototype.toUpperCase).toHaveBeenCalled() expect(spytoUpperCase.calls.count()).toEqual(1) have failed.

If the code you are testing contains asynchronous operations, you need a way to let Jasmine know when the asynchronous operations have completed.

By default, Jasmine waits for any asynchronous operation, defined by a callback, promise or the async keyword, to be finished. If Jasmine finds a callback, promise or async keyword in one of these functions: beforeEach, afterEach, beforeAll, afterAll, and it it will wait for the asynchronous to be done before proceeding to the next operation.

Let’s take our example simulateAsyncOp() which simulates an asynchronous operation using setTimeout(). In a real world scenario this can be an Ajax request or any thing similar that happens asynchronously:

To test this function we can use the beforeEach() function with the special done() callback. Our code needs to invoke done() to tell Jasmine that the asynchronous operation has completed:

We can quickly notice a drawback of this method, so we need to write our code to accept the done() callback. In our case, we didn't hardcode the done() method in our simulateAsyncOp(fn) but we have provided a callback parameter just to be able to call done().

If you don’t want to create code that depends on how you write your test, you can use a promise instead and call the done() callback when the promise has resolved. Or better yet, in Jasmine 2.7+, if your code returns a Promise, Jasmine will wait until it is resolved or rejected before executing the next code.

Jasmine 2.7+ supports async and await calls in specs. This relieves you from putting asserts in a .then() or .catch() block.

This is the implementation of simulateAsyncOp:

The Jasmine clock is used to test asynchronous code that depends on time functions such as setTimeout() in the same way we test synchronous code by mocking time-based APIs with custom methods. In this way, you can execute the tested functions synchronously by controlling or manually advancing the clock.

You can install the Jasmine clock by calling the jasmine.clock().install function in your spec or suite.

After using the clock, you need to uninstall it to restore the original functions.

With Jasmine clock, you can control the JavaScript setTimeout or setInterval functions by ticking the clock in order to advance in time using the jasmine.clock().tick function, which takes the number of milliseconds you can move with.

You can also use the Jasmine Clock to mock the current date.

This is the simulateAsyncOp function:

If your asynchronous code fails due to some error, you want your specs to fail correctly. Starting with Jasmine 2.6+ any unhandled errors are sent to the currently executed spec.

Jasmine also provides a way you can use if you need to explicitly fail your specs:

In this guide we’ve introduced Jasmine and seen how to get started using Jasmine to unit test your JavaScript code. Thanks for reading!

Add a comment

Related posts:

Why Your Roses Smell Nice

The appeal of many floral scents to humans is a fortunate byproduct: We were not even around when they appeared. And, for all the effort, commercial perfumes rarely smell like flowers. Expensive…

Why It Matters If Vegetables are Racist

There was a recent study by a student at California State University San Marcos linking “The Veggie Tales” Christian animated series and movies to racism. The claim is that the characters portrayed…

Science proves why the internet is negative

For at least a couple of years now I have really wanted to create on the internet. I do have to admit that this hesitation was just out of fear of the ridicule that I knew I was in for. Just recently…