Ver en Español
Writing tests for private functions and properties in Angular
Apr 13, 2023
Updated: Jun 25, 2026

Writing tests for private functions and properties in Angular

Let's assume you already know what Angular is and how to create a service. When you run the command ng generate service <service name>, two files are created: the service file and one with the .spec extension. The purpose of the latter is to give you an easy, understandable structure for writing tests.

Fundamentals

Suppose you decide to create a service called names to return a list of names. You'll store the names in a private property and create a get method to return them to a component or another service. It would look something like this:

// Imports the 'Injectable' decorator from '@angular/core' to define an injectable Angular service
import { Injectable } from '@angular/core';

// Applies the 'Injectable' decorator and marks the service as available across the whole app ('root')
@Injectable({
  providedIn: 'root',
})
// Exports the 'NamesService' class as an injectable Angular service
export class NamesService {
  // Defines a private property 'names', a string array with two initial names
  private names: string[] = ['Juan', 'Mati'];

  // Empty constructor for the 'NamesService' class
  constructor() {}

  // Public 'getNames' method that returns the names array
  public getNames() {
    return this.names;
  }
}

In our unit test, you might try something like this:

// Imports 'TestBed' from '@angular/core/testing' to create a testing environment
import { TestBed } from '@angular/core/testing';

// Imports the 'NamesService' class from the 'names.service' file
import { NamesService } from './names.service';

// Describes a test suite for 'NamesService'
describe('NamesService', () => {
  let service: NamesService;

  // Before each test, configure the environment and resolve the service with TestBed.inject
  beforeEach(() => {
    // No need to declare 'providers': the service already uses providedIn: 'root'
    TestBed.configureTestingModule({});
    service = TestBed.inject(NamesService);
  });

  // Test that checks the 'NamesService' service is created correctly
  it('should be created', () => {
    // Expects the service to be truthy (instantiated)
    expect(service).toBeTruthy();
  });

  // Test that checks 'getNames' returns all the names
  it('should return all names', () => {
    // Expects the result of 'getNames()' to equal the 'names' array
    expect(service.getNames()).toEqual(service.names);
    // Expects the first element of 'getNames()' to equal the first element of 'names'
    expect(service.getNames()[0]).toBe(service.names[0]);
    // The first element of the array ['Juan', 'Mati'] is 'Juan'
    expect(service.getNames()[0]).toBe('Juan');
  });
});

Note on the modern API. In older Angular versions it was common to use the inject([NamesService], (service) => {... }) helper inside the it. From Angular 9 onward the idiomatic pattern is to resolve the service with const service = TestBed.inject(NamesService);. The inject() helper still exists, but the official docs recommend TestBed.inject.

However, when you try to read the service's names property you'll hit a problem: it's private, and TypeScript won't let you access it from the test. The compiler will flag an error. To work around this, you can skip TypeScript's check and reach the object through bracket notation, like service["names"]. Here's how it would look:

// Imports 'TestBed' from '@angular/core/testing' to create a testing environment
import { TestBed } from '@angular/core/testing';

// Imports the 'NamesService' class from the 'names.service' file
import { NamesService } from './names.service';

// Describes a test suite for 'NamesService'
describe('NamesService', () => {
  let service: NamesService;

  // Before each test, configure the environment and resolve the service with TestBed.inject
  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(NamesService);
  });

  // Test that checks the 'NamesService' service is created correctly
  it('should be created', () => {
    // Expects the service to be truthy (instantiated)
    expect(service).toBeTruthy();
  });

  // Test that checks 'getNames' returns all the names
  it('should return all names', () => {
    // Reaches the private 'names' property with bracket notation
    // Expects the length of 'getNames()' to equal the length of 'names'
    expect(service.getNames().length).toBe(service['names'].length);
    // Expects the first element of 'getNames()' to equal the first element of 'names'
    expect(service.getNames()[0]).toBe(service['names'][0]);
  });
});

Important: the limits of this technique. TypeScript's private modifier exists only at compile time, it's a hint for the compiler, not a real barrier at runtime. That's why service["names"] works: in the generated JavaScript the property is perfectly accessible. This is different from JavaScript's native private fields, the ones that start with # (for example #names), which really are private at runtime and cannot be reached with bracket notation. If your property is #names, this technique does not apply.

There are debates about whether testing private properties is correct and whether this syntax is appropriate. The answer will depend on your team's philosophy and the style guide you adopt. It's useful to have a "Swiss army knife" of options for different cases and projects.

Conclusions

  • You can access private properties in unit tests using JavaScript bracket notation, because private in TypeScript is only a compile time check.
  • The approach you pick may depend on your team's philosophy and style guide.
  • Having different testing options is useful to adapt to different cases and projects.

Suggested exercises

  1. Create an Angular service that manages a list of objects instead of a list of names and adapt the unit tests to test its private properties and methods.
  2. Write a set of tests that verify items can be added to and removed from the service's list of objects, while also testing the private properties.
  3. Research and compare different approaches for testing private properties and methods in Angular and discuss with your team which is most appropriate for your projects.

3-point summary

  1. Use JavaScript bracket notation (service["names"]) to access private properties in Angular unit tests, remembering that this only works with TypeScript's private, not with #private fields.
  2. Resolve services with TestBed.inject(NamesService), the current idiomatic pattern, and avoid declaring providers when the service already uses providedIn: 'root'.
  3. Pick the right testing approach based on your team's philosophy and style guide, and keep several options on hand for different cases and projects.

That's all, I hope this post is useful to you and that you can apply it to a project you have in mind. If you have any questions, leave me a comment below. And remember, if you liked it, you can also share it using the social links at the bottom.

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Advertisements

Related Posts

Categorias