avatar

KONNECTWAY

Posted on Sep 17, 2024

JEST: Unit and Integration Testing

#JEST #Unit Test

 7 mins of reading


Unit Testing vs. Integration Testing

When it comes to building reliable software, testing is essential. Some might think tests are a waste of time, but let’s be real—would you drive a car that’s never been tested by the manufacturer?  I didn’t think so!

Two common types of tests in software are unit tests and integration tests. But what’s the difference? Let’s break it down with an easy comparison.


Unit Testing: The Wheels

Think of unit tests as inspecting the individual parts of a car. For example, you might check just one wheel to make sure it’s round, sturdy, and spins correctly. In coding, unit tests focus on small, isolated pieces of your code—like functions or methods—to make sure they work properly on their own.

Why is this important? Well, even if 99% of your car is working, if one wheel is faulty, the whole car won’t drive smoothly (or at all). The same goes for code: if one function breaks, it could cause bigger problems down the line.


Integration Testing: The Whole Car

Now, imagine testing all four wheels once they’re attached to the car. Integration tests do exactly that—they check if different parts of your code work well together. Even if each wheel passed its individual test, there’s no guarantee they’ll function correctly when combined with the rest of the car.

In software, integration tests ensure that different modules or components of your application play nicely together. This is important because even well-written individual pieces of code can still cause errors when they interact in unexpected ways.


What Makes a Good Test?

When writing tests, there are a few essential characteristics to keep in mind. A good test should be:

  • Readable: Easy to understand at a glance.
  • Writable: Simple to create and maintain.
  • Reliable: Consistent results, no false positives.
  • Fast: Shouldn’t slow down your development process.
  • Mostly Unit Tests: Focus on testing small, isolated parts of your code.

The last point follow the simple idea: if you test all the small pieces (unit tests), everything should work well when those pieces come together (integration tests).


The AAA Pattern: Arrange, Act, Assert

One of the simplest and most effective approaches, to write unit test is the AAA pattern—which stands for Arrange, Act, and Assert. This pattern keeps your tests clean, organized, and easy to understand.

  • Arrange: Prepare the data, objects, or conditions needed for the test.
  • Act: Execute the function or code that you want to test.
  • Assert: Verify the result by checking if the outcome matches what you expect.

Step 1: Arrange

Let’s start with Arrange, the first step of the AAA pattern. This is where we set up the initial state before running our test. It’s often referred to as setting up the subject under test (SUT), which is the piece of code that will be tested. In this step, we typically initialize variables, import necessary modules, and prepare the environment.

For example:

// Arrange
const message1 = "Hello World";

Here, we’re preparing message1 as input for the function we want to test. The Arrange step ensures everything is ready without actually running the test logic.


Step 2: Act

Next, we move on to Act, where we apply actions or stimuli to the SUT. This could involve calling methods, simulating user interactions, or triggering events.

// Act
const message2 = message1.trim();

In this case, we are trimming the value of the message1 variable. The Act step is all about executing the behavior that we want to test.


Step 3: Assert

The final step in the AAA pattern is Assert. Here, we observe and verify the results by checking if the outcome matches our expectations.

In Jest, we typically use functions like expect() and toBe() to compare the actual result with the expected one.

// Assert
expect(message2).toBe("Hello World");

In this case, we’re asserting that message2 is exactly what we expected: "Hello World". If the result matches, the test will pass; if not, it will fail, indicating that something went wrong.


Getting Started with Jest

Before we dive into Jest, it’s worth mentioning that all the examples we’ll cover are available on Codesandbox.io a great tool for quickly testing and experimenting with code online.

Jest is a JavaScript testing framework that makes it simple to write and run tests. With features like mocking, code coverage, and easy-to-read results, Jest is designed to help you write efficient and reliable tests.

In this post, we’ll be focusing on unit testing, since, as we mentioned earlier, most tests in programming are unit tests. Unit tests play a crucial role in ensuring that individual components or functions in your code work as expected. They’re the foundation of a solid testing strategy, allowing us to catch issues early and maintain the quality of our codebase.

Jest – expect – toBe

In Jest, the expect function is used to make assertions. It’s like saying, “I expect this value to be something specific.” You can pair expect with various matchers, and one of the most commonly used matchers is toBe.

The toBe matcher checks if the result of an expression matches the expected value, similar to the strict equality operator (===) in JavaScript. For example:

  • expect(1 + 1).toBe(2);  This assertion will pass because 1 + 1 equals 2.
  • expect(1 + 1 + 1).toBe(2);: This assertion will fail because 1 + 1 + 1 equals 3, not 2.

You can find a full list of matchers in the Jest Expect Documentation. Don’t worry if this seems a bit overwhelming at first—most of these functions do the same thing but in different scenarios or with diferents types of variables.

The test() function in Jest is used to define individual test cases. It takes two arguments:

1. Test Name: A string describing what the test is checking. This helps identify the purpose of the test and makes the output more readable.

2. Test Function: A function that contains the actual test logic, including any assertions you want to make.

  • “expect-to-be”: This is the name of the test. It describes that you’re testing the expect function with the toBe matcher.
  • Test Function: Inside the function, you’re using expect() with toBe() to compare values. The first assertion will pass because 1 + 1 equals 2, but the second one will fail because 1 + 1 + 1 equals 3, not 2.

Naming Your Test Files

When working with Jest, it’s a good practice to name your test files in a way that clearly indicates their purpose. Typically, the name of the test file should mirror the name of the file containing the code you’re testing, but with a .test.js extension. This way, Jest can automatically detect your test files and run them.

Some developers prefer to use .spec.js instead of .test.js. The reason behind this is that “spec” stands for “specification” and reflects what they expect the code to do. In both cases, the purpose is the same: to clearly indicate that the file contains tests.

Additionally, it’s common practice to organize tests in a separate folder, often called /tests, that mirrors your source code folder structure.

In CodeSandbox, you’ll find the examples we’re working with organized in a simple, clear structure like the one shown below:

In this structure, we have several JavaScript files, each containing a function or piece of logic to be tested, along with corresponding test files. The test files follow the .test.js naming convention to ensure Jest can easily recognize them.

Here’s a breakdown of the file organization:

  • 01-hello.js: Contains a function, getHello(), that returns a greeting.
  • 01-hello.test.js: The test file for 01-hello.js, where we test the getHello() function.
  • 02-obj.js and 02-obj.test.js: Similar structure, where we have code and its corresponding test for an object-based function.
  • 03-arr.js and 03-arr.test.js: Tests for an array-based function.

Additionally, the index.test.js file contains extra examples of various test cases that demonstrate how to work with Jest and expect functions. This file includes general examples that can help you understand how to set up and execute tests for different scenarios.


Exporting and Importing Files

As you can see in the image, files should be properly exported and imported. In the example:

  • The getHello function is exported from 01-hello.js.
  • In 01-hello.test.js, we import the getHello function using import { getHello } from "./01-hello";.

Jest literally reads the lines of code from the file being tested, so it’s essential to properly import the functions or modules that you want to test.

In this example, the test file correctly imports getHello and runs a simple test using expect() and toBe() to check if the function returns the expected string.


Conclusion

Testing is essential for building reliable software, and Jest makes it easier to write, organize, and run both unit tests and integration tests. While unit tests focus on small, isolated pieces of code, ensuring they work as expected, integration tests ensure these pieces interact correctly when combined.

Jest provides powerful tools for both testing levels. We’ve focused on unit tests here, but Jest is equally effective for integration tests. By applying practices like the AAA pattern (Arrange, Act, Assert) and organizing your test files properly, you can maintain clean, reliable code, ensuring everything from small functions to larger system interactions works smoothly.

In future posts, we’ll explore testing React applications using Jest to ensure the UI behaves as expected. With these basics in hand, you’re ready to dive deeper into Jest’s advanced features. Happy testing!