DEV Community

Cover image for Testing private methods in Typescript
Dany Paredes
Dany Paredes

Posted on • Updated on • Originally published at danywalls.com

Testing private methods in Typescript

Sometimes we need to test a private method, and Yes, maybe it's not a best practice, but in my case have a situation when need adds 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' ;
  }
}
Enter fullscreen mode Exit fullscreen mode

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 change 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);
  }
}
Enter fullscreen mode Exit fullscreen mode

In our test instead, to call the original service original, 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');
  });
Enter fullscreen mode Exit fullscreen mode

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 string literal.

  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');
  });
Enter fullscreen mode Exit fullscreen mode

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 so easy to test the private dependency.

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

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

Thanks to @Lars Gyrup Brink Nielsen )

Summary

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

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

Pick the best for you :)

Top comments (5)

Collapse
 
vjrngn profile image
Vijay Rangan

Nice post. I like the ['handleError'] approach. A note though. Exporting a function from another file technically has the same effect as making the method public; its just not scoped to the instance.

Collapse
 
layzee profile image
Lars Gyrup Brink Nielsen

Another option is to extract it to a separate file, export it in that file, and import it in the original file.

Collapse
 
danywalls profile image
Dany Paredes

True! clean approach :D

Collapse
 
layzee profile image
Lars Gyrup Brink Nielsen

Dany, you're missing the function keyword after export in the new code snippet for the exported function. I appreciate the credits and edit.

Collapse
 
ajithsimon profile image
Ajith Kumar • Edited

@danywalls I really have a similar setup in my react project. But always my public function returns null in my test case. :(

stackoverflow.com/questions/727677...