Comparing the top 3 Javascript testing frameworks

Michael Bogan - Mar 18 '20 - - Dev Community

Introduction

Every developer knows that writing tests is important, but when it comes to JavaScript testing, there are many frameworks to choose from. So, the next time you start a project, how do you know which framework to choose?

In this article, I'm going to compare three popular frameworks—Mocha, Jest, and Jasmine—to help you make a more informed decision. I'll look at how these frameworks handle common test scenarios, such as mocking functions and asynchronous calls. I'll show examples of how to implement these tests. I'll also talk a little about best practices and why you should use a testing framework.

The Three Frameworks

Mocha, Jest, and Jasmine are all popular frameworks with helpful communities and years of development. Overall, Mocha and Jasmine are stronger for testing the back end because they were initially built for Node applications; therefore, they have more back-end tools and documentation available than Jest. For the front end, your testing framework choice is usually influenced by your front-end framework. Jasmine is used more often with Angular, and Jest was created by Facebook to use with React.

Regardless of which of these frameworks you choose, all three are mature and effective choices. The best choice will really come down to the needs of your project and your personal preferences. To help you decide which framework is best for you, let's look at each framework in action under some common testing scenarios.

Mocking Functions

The most common thing you'll test in your applications is function calls. It's important to write solid tests for your functions, because regardless of the testing framework, poor tests can trigger real functions, which leads to memory leaks and unexpected behavior in the browser.

When testing function calls, your tests should:

  • focus on the expected results from your function calls, not the implementation of the function
  • never make changes to the state of your application
  • use mock functions so you don't have to worry about unintended side effects crawling through your tests

Here are examples of how to mock function calls on the front end in Jest, Jasmine, and Mocha.

Jest

If you use React, Jest doesn't require many dependencies, if any. However, if you don't want to dig into the react-testing-library, Jest is also compatible with some of the testing specific libraries like Enzyme. This example uses Enzyme to do a shallow render of a component, click a button, and then see if a modal has been opened. Here you have to render the component and simulate a click to see if your mock function call opens the modal as expected.

A mock function call in Jest
A mock function call in Jest

Jasmine

Out of all of the frameworks, Jasmine is better suited for Angular. However, once you have all of the proper configs and helper files set for React, writing the tests doesn't require much code.

Here you can see ReactTestUtils being used instead of Enzyme or react-testing-library (to show one of the other tools available). ReactTestUtils makes it easier to work with Jasmine on the front end, keeping the lines of code low. However, you'll need an understanding of the ReactTestUtils API.

A mock function call in Jasmine
A mock function call in Jasmine

Mocha

Mocha gives you a little more flexibility because it's commonly used for both front-end and back-end testing. You'll have to import several libraries, like Chai, to get it to work with React. While Chai isn't directly connected to React, it is the most commonly used assertion library used with Mocha. Once those dependencies are installed, it's similar to working with Jest. This example is using a combination of Enzyme for rendering and Chai for assertions.

A mock function call in Mocha
A mock function call in Mocha

My Take

For mocking functions, all three libraries are very similar in the lines of code and complexity. I recommend simply using the library that works best with your stack: Jest for React, Jasmine for Angular, and Mocha, if you're also using Mocha on the back end and want to stay consistent.

Mocking Data

Testing on the back end is as tricky as testing on the front end. This is especially true with handling data, as you don't want your tests to insert data into your real database. This dangerous side effect can easily sneak into your test suites if you aren't careful. That's why setting up a test database with mock data is best practice.

When you use mock data you can:

  • see exactly where errors occur because you know what values to expect
  • type check your back-end responses and ensure responses aren't revealing real data
  • find bugs faster

Mocking data to send in your requests is something you'll encounter often, and something all three of these frameworks support. Here are examples of how the three frameworks implement mocking data.

Jest

The most important thing to note in this Jest test is how you check to see if your data was successfully passed to an API or database. There are several expect() matchers at the end and their order is important. You have to tell Jest exactly what you expect to find after you send your mocked data. The supertest library is in use here to make the mock post request with the fake data.

Handling data in a Jest back-end test
Handling data in a Jest back-end test

Jasmine

While it takes a bit more code to write a good back-end Jasmine test, you can control how and when data is created and reset. Jasmine also has built-in tools for referencing your mock data in other parts of your test. This example uses the request library to handle mock post data requests.

Handling data in a Jasmine back-end test
Handling data in a Jasmine back-end test

Mocha

Out of all the frameworks, Mocha requires the most dependencies for working with mock data and requests. You might need to set up a mock server with chai-http to run the requests instead of mocking the request and response as with the others. Mocha does have good built-in tools, but they require more time to get started. Using Chai and its associated libraries is a common practice, as seen in this example:

Handling data in a Mocha back-end test
Handling data in a Mocha back-end test

My Take

Back-end testing is where Mocha and Jasmine are strongest. They were built for testing Node applications and it shows in their tools. They give you a more fine control through options and functionality that are included with the framework. Jest can still be a great option if you are willing to take the time to add some of the available libraries.

Mocking Async Calls

Asynchronous code is known for causing issues, so testing here is especially important. Not only do you have to watch for asynchronous behavior in your own code, but many bugs that make it through to production can come from unexpected asynchronous sources such as third-party services. When you are writing tests with async behavior, try to avoid triggering real function calls due to the tests' async calls overlapping with the real code's execution.

All of the testing frameworks provide you with multiple options for writing async code. If your code is using callbacks, then your tests can use callbacks. If you have the option, consider using the async/await pattern. It makes your code more readable and helps you quickly find where your tests are breaking.

Promises are also an option for writing async tests. Consider using these if you are working with older code that doesn't support async/await. However, make sure that they are executing in the order you would expect in production. Checking your values throughout execution can help catch odd behavior.

Here are examples of async testing in the JavaScript testing frameworks.

Jest

Although it's straightforward to write back-end tests with Jest, since it was originally create to use with React, you'll probably end up spending some time in the documentation and installing third-party libraries because most of its tools are front-end specific. But Jest does handle async calls in any format you need to handle them, like callbacks or Promises. This async/await call works the same way as it does in your other JavaScript code.

Jest async call using async/await
Jest async call using async/await

Jasmine

Jasmine was initially created for Node.js, so it has a lot of built-in functionality. However, it can take a bit of setup before and after running tests. For instance, you can see here that you should handle the async call within the beforeAll method to prevent residual effects later in the tests.

Jasmine async call using async/await
Jasmine async call using async/await

Mocha

Here you can see the done method used to handle returning a promise. It's using the same chai-http library as in the previous Mocha example. This is a common way you'll see async calls written in Mocha tests. You can use Promises or the async/await pattern with Mocha.

Mocha async call using async/await
Mocha async call using async/await

My Take

For back-end testing, Jasmine handles asynchronous methods easily and out of the box, and would be my first choice. Mocha and Jest are useful as well, although they take more searching through documentation to find what you need.

Mocking Rendered Components

Another important and common test is making sure rendered components are available when expected. As before, you'll typically see Jest used with React, and Jasmine used with Angular, but you can use any of the three frameworks on any of the front-end libraries.

Rendering components can be an expensive task depending on the depth rendered. Some developers prefer to use snapshot testing, which saves a file that represents the current state of the UI. Others prefer mocking rendered components. Snapshots are more useful when you are looking for changes in the UI while rendering is more useful when you want to see if your components are working as expected. Both methods are useful.

Jest

As I mentioned earlier, Jest is built for React, so you won't have to import any special libraries to do render testing. That keeps these tests light and saves space on dependencies. ReactDOM is common in many React projects and comes with the methods you need to check basic rendering as shown in this example below:

React render test in the Jest framework
React render test in the Jest framework

Jasmine

Setting up Jasmine to work for React render testing is harder than it might seem; it involves significant initial setup. The Angular team uses and recommends Karma and Jasmine for testing components. The example below is for testing an Angular component. You have to import the component you want to test and you can use @angular/core/testing, which comes with Angular, to set up the environment for the component before trying to render it and check if it is available.

Angular render test in the Jasmine framework
Angular render test in the Jasmine framework

Mocha

You'll usually see Enzyme and Chai used with Mocha for front-end testing and testing React rendering isn't any different. Once you have imported the specific methods you need, like shallow and expect, you'll be able to write tests similar to the other frameworks. The example below takes advantage of Enzyme's shallow rendering and Chai's assertions.

React render test in the Mocha framework
React render test in the Mocha framework

My Take

The best practice for rendering components is to just use the testing framework recommended for your front-end library. Use the tools that come installed and you won't need to handle configuration errors. If possible, try to use shallow renders and snapshots to save time on your tests and to focus on the core functionality of the rendered components.

Conclusion

Hopefully you now have a better idea of the differences among these three popular frameworks. As I mentioned, regardless of which framework you choose, all three are mature and effective choices and can work for you, depending on the need of your project and your preferences. Now you're ready to get testing!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .