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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
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.
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.
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.
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.
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.
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:
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.
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.
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.
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!