How To Test Private Methods in Typescript

3 Ways to Test Private Methods or dependencies

Sometimes we need to test a private method, and Yes, maybe it's not a best practice, but in my case, I have a situation when I need to add a test to my private method, and if you have the same situation I hope it helps you.

My example is a service with a private method for error. We want to be sure the error method private returns the message.

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class ProductService {
  constructor(private httpClient: HttpClient) {}

  getProducts(): Promise<any> {
    return this.httpClient
      .get('/api/products')
      .toPromise()
      .then((response: any) => response.data)
      .catch(() => {
        this.handleError(new Error('Failed to get products'));
        return [];
      });
  }

  private handleError(error: Error): string {
    return 'Ups a error' ;
  }
}

We can't test because it is private, we can't test, but we have two approaches to cover test the handleError method.

  • Change signature to protected.
  • Use array access for the private members
  • Extract the private logic to file and export. (Thanks to Lars Gyrup Brink Nielsen )

Change signature from private to protected.

We changed the signature for our private method to protected. It implies that the method is accessible only internally within the class or any class that extends it but not externally.

The next step is to create an extended class and call the method from a public method, and we test the public implementation in our test.

class ProductServiceExtend extends ProductService {
  constructor(httpClient: HttpClient) {
    super(httpClient);
  }
  handleErrorExtended(error: Error): string {
    return this.handleError(error);
  }
}

Instead, in our test, to call the original service, we use the extended class and call the public implementation.

  test('should return error message', () => {
    const errorMessage = new ProductServiceExtend(null).handleErrorExtended(
      new Error('Failed to get products')
    );
    expect(errorMessage).toBe('Ups a error');
  });

Use array access for the private members

If you don't want or can change the signature, then, we can use ['handleError'] to access it without changing the signature.

Some linter rules don't like to give access to objects using a literal string.

  test('should print error in console', () => {
    const spy = jest.spyOn(console, 'error');
    const errorMessage = new ProductService(null)['handleError'](
      new Error('Failed to get products')
    );
    expect(errorMessage).toBe('Ups a error');
  });

Move the private function to file and export

A great and clean option is to move the private function into another file and export it into the service and the test because It makes it easy to test the private dependency.

export function handleError(error: Error): string {
    return 'Ups a error';
  }

Next, import the function into the service and the test.

Thanks to Lars Gyrup Brink Nielsen )

Summary

I show three options, but in my opinion, moving the private function into a file and exports it is the best one, keeping your code clean and maintainable. If you don't want to create an additional file, you may declare it into the same file.

Change the signature good alternative but, of course, required to create an extra class as sandbox protected and extending the class as a sandbox to work with it.

Pick the best for you :)