Tips for Unit Testing in Front-end Frameworks: Part 1

If you’re a developer who has primarily learned testing from writing basic unit tests in languages like C++, writing comprehensive unit tests in front-end frameworks can feel like learning a whole new language. Instead of just testing a function based on a given input and expected output, front-end testing introduces new layers of complexity.

When I first started on my current project at Atomic Object, an Angular project with tests written in Jest, the most challenging aspect of the project was writing tests for my code. While I could verbalize the functionality I wanted to test, when I tried to sit down and write a test for it, I’d struggle to translate my thoughts into effective tests. After a while, I was able to determine I was having a difficult time because I hadn’t developed any true instincts for front-end testing.

In this two-part series, I’m going to share what I’ve learned about writing unit tests in front-end frameworks, specifically using Jest in an Angular project. In this first installment, we’ll discuss the mindset shift that helped me reframe unit tests in front-end frameworks in a way that made them more recognizable and easier to write.

Shifting Your Mindset

The biggest roadblock I faced when learning to write good unit tests for front-end projects was shifting my mindset to account for the extra layers of complexity involved in most front-end frameworks. If you’re like me, the unit tests you’re used to writing in college or for personal projects have a very basic set up.

  • Function to test.
  • Input.
  • Expected output.
  • Actual output.

After plugging your input values into the function you want to test, if Expected Output == Actual Output, your tests pass. Otherwise, they fail.

Testing the front-end of an application, however, involves extra layers such as communicating with the backend, mocking services, and spying on functions. At first, it felt like these extra components required an entirely new model of thinking. The truth is, however, you can still apply the general outline of input, actual output and expected output to your front-end tests. You just have to think of the other “stuff” as prerequisites to your simple test. I found that the simple act of viewing my testing process as accomplishing “prerequisites” and then writing a simple, familiar input/output test helped me break down the complexity of front-end unit testing.

Determining What to Test

Let’s start by breaking down a basic test written to assert that an alert with the correct error message and success messages are displayed upon an update to a user’s password for a site. Today we’ll focus on determining what we should test and framing it in a more digestible way for the inexperienced front-end tester. Remember: we can still use our input/expected output/actual output frame of reference for testing in the front end. We just need to make sure we finish our prerequisites, too. Here’s a snippet of a very basic toy authentication component I wrote that makes a request to a toy server with a new password to update the user’s sign-in information.


public sendPasswordUpdateRequest(password: string): Observable<HttpResponse> {
    return this.http.patch(
      this.apiUrl,
      { password },
      { observe: 'response', responseType: 'text' as 'json' }
    ).pipe(
      catchError((error: HttpErrorResponse) => {
        console.error('Error updating password:', error);
        return of(error as any);
      })
    );
  }

  public updatePassword(newPassword: string): void {
    this.sendPasswordUpdateRequest(newPassword).subscribe({
      next: (res: HttpResponse) => {
        alert("Password updated successfully!");
      },
      error: (err: any) => {
        console.error('Error updating password:', err);
        alert("Failed to update password.");
      }
    });
  };

Code Breakdown

As a user, I’m making changes to my sign in data. This will happen via an HTTP patch request carrying the data I want to change – my password. As a developer, I want to make sure that my application’s user interface properly informs the user of the status of their request. If a user’s request fails for any reason, I want the alert message that is displayed to contain a custom error message. If it succeeds, I want a custom success message to be displayed.

Your first instinct when applying the input/expected output/actual output model to this test might be that our “input” is the data we’re changing, i.e. the new “password” value being passed in. Or that it’s the initial HTTP patch request with the updated password. Neither is quite right, unfortunately.

Let’s take a step back and think about what we’re actually testing. Are we testing that certain inputs for email & password are rejected and others accepted? No, that would be more along the lines of authentication/validation. Are we testing that the HTTP patch request succeeds or fails? Again, not quite.

We are testing that when a request is made and throws an error, the correct user-friendly error message is displayed, or that when a request is made and succeeds, the correct user-friendly success message is displayed in the application’s window. Therefore, our “input” would be the response from an HTTP Patch request with updated password data. Our “output” is one of two alert messages: a success message, and a failure message.

Now that we’ve distilled our test into a more digestible & recognizable format, we can move onto completing our prerequisites.

Determining Your Test’s “Prerequisites”

For the most part, the clues that will help you determine the “prerequisites” for your unit test lie in the code you’ve already written. For example, we know that since we’re testing how successful HTTP responses are handled as well as ones that throw errors, we’ll need “fake” HTTP responses. And, since these responses are returned by a function within our code, we’ll need a way to pass them as data returned from that function. We’ll touch more on this in the next part, but, as a preview, this means we’ll need to use jest.spyOn(), mockReturnValue(), and other functionality.

By continuously applying this method of matching up source code to testing prerequisites, we can easily determine what we need to set up in our tests beyond our input and expected output.

Testing in Front-end Frameworks: What’s Next

In this installment, we reviewed some tips to distill your front-end tests into more manageable and recognizable tasks. By stopping to reconsider how we apply the traditional “input/expected output/actual output” testing method to front-end functionality and learning how to separate out testing prerequisites from our core test, we’ve properly prepared ourselves to sit down and write tests for an Angular application’s component using Jest’s helpful framework. In my next post, we’ll dive deeper into completing our testing prerequisites as well as the core syntax of jest tests and learn how to translate our test breakdown into an effective unit test (or two).

Conversation

Join the conversation

Your email address will not be published. Required fields are marked *