Jeremy Moritz
Jeremy Moritz

Reputation: 14932

spyOn isDevMode() in Angular 6+ Unit Tests?

My code includes an if block like this

Service:

import { isDevMode } from '@angular/core';

export class MyService {
  constructor() {}

  methodToTest(): {
    if (!isDevMode()) {
      doSomething();
    } else {
      doSomethingDifferentInDevMode();
    }
  }
}

my-service.spec.ts (the relevant lines only):

it('should run doSomething() when isDevMode() returns false', () => {
  // what should i do here to mock a false value return for isDevMode() ?
  expect(doSomething).toHaveBeenCalled();
});

How do i spyOn the isDevMode() to make it return false for this single Angular unit test so that i may test that doSomething() is called?

Upvotes: 7

Views: 3333

Answers (4)

Daniel Gimenez
Daniel Gimenez

Reputation: 20609

Injecting a service as suggested by the accepted answer isn't always a possibility. Here is a safe (as possible) approach.

The primary issue here is that there's no disableProdMode, so if you use enableProdMode one time then all future tests will have "prod" turned on.

Fortunately "prod" mode is just derived from a global variable, ngDevMode which is an object or false. You can see its role in enableProdMode here. You just have to make sure to restore it as soon as you're done with it as there is the potential for nasty side effects.

declare let ngDevMode: object | false;
if (typeof ngDevMode === 'undefined' || !ngDevMode) {
  throw new Error('These tests must be launched in dev mode.');
}
const originalDevMode = ngDevMode;

describe('my prod tests', () => {
  beforeEach(() => ngDevMode = false);
  afterEach(() => ngDevMode = originalDevMode);
  /* tests... */
});

In the example above, ngDevMode is being set and restored before and after each test. This works in my cases, but I've seen examples where this can still have undesirable side effects. So you might actually have to set and unset it before and after a specific line.

Upvotes: 0

The approach that I'm using is the following

import { enableProdMode, isDevMode } from '@angular/core';
declare const global: any;

describe('teststing IsDevMode', () => {
    beforeEach(() => {
        /**
         * We must reset the devmode to true before each test, as it is the default non prod state
         *
         */
        global['ngDevMode'] = true;
    });

    it('should be true in tests', () => {
        expect(isDevMode()).toBe(true);
    });
    it('should be false for prod using ngInternals', () => {
        enableProdMode();
        expect(isDevMode()).toBe(false);
    });
    it('should be false for prod', () => {
        global['ngDevMode'] = false;
        expect(isDevMode()).toBe(false);
    });
});

So basically we are overwriting the global variable used for checking if the project is in devMode. You can find out how the internals work here: AngularGitHub

Upvotes: 0

JB Nizet
JB Nizet

Reputation: 692181

Start by wrapping this function into a service:

import { isDevMode, Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class DevModeService {
  constructor() {}

  isDevMode() {
    return isDevMode();
  }
}

Then you can inject this service wherever you want, as you would do with any other service:

import { DevModeService } from 'dev-mode.service';

export class MyService {
  constructor(private devModeService: DevModeService) {}

  methodToTest(): {
    if (!this.devModeService.isDevMode()) {
      doSomething();
    } else {
      doSomethingElse();
    }
  }
}

And you can then spy on the DevModeService when testing MyService, like you would do with any other dependency:

it('should do something when in dev mode') {
  const devModeService = TestBed.inject(DevModeService);
  spyOn(devModeService, 'isDevMode').and.returnValue(true);

  const myService = TestBed.inject(MyService);
  myService.methodToTest();
  // ...
}

it('should do something else when in prod mode') {
  const devModeService = TestBed.inject(DevModeService);
  spyOn(devModeService, 'isDevMode').and.returnValue(false);

  const myService = TestBed.inject(MyService);
  myService.methodToTest();
  // ...
}

Upvotes: 12

robrich
robrich

Reputation: 13205

Looks like the answer is in the comments. Thanks @JBNizet.

// dev-mode-service.ts

import { isDevMode } from '@angular/core';

export class DevModeService {
  constructor() {}

  isDevMode(): {
    return isDevMode();
  }
}


// MyService.ts

import { isDevMode } from 'dev-mode-service';

export class MyService {
  constructor() {}

  methodToTest(): {
    if (!isDevMode()) {
      doSomething();
    } else {
      doSomethingDifferentInDevMode();
    }
  }
}

Upvotes: 0

Related Questions