DEV Community

loading...
Cover image for Don´t use Singleton Pattern in your unit tests

Don´t use Singleton Pattern in your unit tests

Abú-Bakr Pereira Kebé
APIs, Android Apps, SQL/NoSQL, Python, Kotlin,TS, Docker.💻🖥🐍 etc
Updated on ・3 min read

What is Singleton Pattern?

Is a design pattern that restricts the instantiation of a class to a single instance. This is useful when one object is needed to coordinate actions across the system exemple custom spiner and services. Through this design pattern, the singleton class ensures that it´s only instantiate once, and can provide easy access to the single instante.

Unit Testing

Is a type of software testing where individual units or componentes of a software are tested. Unit tests isolate a section of code and verify its correctness. A unit test may be an individual function, method, procedure, module or object.

Why do some programmers use singleton in testing?

Some programmers use Singleton in their tests with the intention of using the DRY (Don´t repeat yourself) principle by instantiating the classes that will be used in the tests only once.

So, why don't use design pattern singleton in Unit tests?

Considering that one of the best practices in unit testing is that tests should be independent and in case of any change in one test the others should not be affected, the singleton pattern hinders testability.

Singletons cause implicit dependencies between conceptually independent units of code. This is problematic both because they are hidden and because they introduce unnecessary coupling between units.

As there is no control over creation, a clean instance of the object cannot be used for each test.

Example

I'm going to use TypeScript and Jasmine testing framework in this example.

State.ts Model

export class State {
  id: number;
  status: string;
  static instance:State;

  constructor(args: {id:number,status: string}) {
    this.id = args.id;
    this.status = args.status;
  }

  static getInstance():State{
    if(!State.instance){
      State.instance = new State({id:1995,status:'LOADING'});
    }
    return State.instance;
  }
}
Enter fullscreen mode Exit fullscreen mode

state.spec.ts

import { State } from .....;

describe('State', () => {
  let state:State = State.getInstance();

  it('UT1 - should state id and status be correct', () => {
    expect(state.id).toEqual(1995); // Will always pass
    expect(state.status).toEqual('LOADING'); // Will always pass
    state.status = 'STOP';
  });

  it('UT2 - should state status be LOADING', () => {
    expect(state.status).toEqual('LOADING'); // It will fail whenever UT1 is run first because the state will always change to STOP
  });

});
Enter fullscreen mode Exit fullscreen mode

UT1 (Unit Test 1) and UT2 share state of the same instance of a class causing unnecessary coupling between unit tests.

To prevent this from happening we need to perform some setup.

These activities are called setup and teardown (for cleaning up) and Jasmine has function we can use to make this easier.

First we need to add this code in State.ts

static getCleanInstance():State{
    return new State({id:1995,status:'LOADING'});
 }
Enter fullscreen mode Exit fullscreen mode

And now we add this code in state.spec.ts
In each test we get a new instance of state eliminating the dependency between tests.

let state:State;
  beforeEach(() => {
     state = State.getCleanInstance();
  });
Enter fullscreen mode Exit fullscreen mode

Conclusion

Singleton classes do not allow for Test Driven Development (TDD) The laws of TDD.

Unit testing depends on tests being independent of one another, so the tests can be run in any order and the program can be set to a known state before the execution of every unit test. Once you have introduced singletons with mutable state, this may be hard to achieve. In addition, such globally accessible persistent state makes it harder to reason about the code, especially in a multithreaded environment.

The Singleton pattern solves many of your problems. You know that you only need a single instance. You have a guarantee that this instance is initialized before it’s used. It keeps your design simple by having a global access point, but don’t use in your unit tests.

Cheers ✌️,
Bc.

Discussion (2)

Collapse
lexlohr profile image
Alex Lohr

You can still use new mySingleton.constructor() for testing purposes if your singleton has persistent states. Yes, it's a tiny bit less elegant. No, it's not impossible to use a singleton in TDD.

Collapse
vivaldi profile image
Vivaldi Silva

Nice article. 👌🏾