Angular: Is there a way to mock the value of PLATFORM_ID in a unit test?
Asked Answered
D

2

9

I am using Angular Universal. I have a guard for a route that behaves differently depending on if I am running on the server or the browser platform. Here is the guard:

export class UniversalShellGuard implements CanActivate {
  private isBrowser: boolean;

  constructor(@Inject(PLATFORM_ID) private platformId: Object) {
    console.log('PLATFORM_ID = ' + platformId);
    this.isBrowser = isPlatformBrowser(this.platformId);
  }

  canActivate(): Observable<boolean> | Promise<boolean> | boolean {
    return !this.isBrowser;
  }
}

As you can see, the guard is injecting PLATFORM_ID and uses it to determine if he canActivate() or not.

Now, I wanted to write a simple unit test for the guard and did the following:

describe('UniversalShellGuard', () => {
  let guard: UniversalShellGuard;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        UniversalShellGuard,
        // Idea: for now I just want to test the behaviour if I would be on the browser, so I would just use a fixed value for PLATFORM_ID
        { provide: PLATFORM_ID, useValue: PLATFORM_BROWSER_ID },
      ],
    });

    guard = TestBed.get(UniversalShellGuard);
  });

  it('should deny', () => {
    expect(guard.canActivate()).toBe(false);
  });
});

But it gives the following error:

ERROR in ./src/app/universal-shell.guard.spec.ts
Module not found: Error: Can't resolve '@angular/common/src/platform_id' in '/my-app-path/src/app'
 @ ./src/app/universal-shell.guard.spec.ts 4:0-70 11:50-69
 @ ./src sync \.spec\.ts$
 @ ./src/test.ts

I even tried a simple and straight forward construction of the guard, without using the angular TestBed:

it('should deny', () => {
  const guard = new UniversalShellGuard(PLATFORM_BROWSER_ID);
  expect(guard.canActivate()).toBe(false);
});   

Same error.

Is there any way to provide a fixed value for PLATFORM_ID for properly unit-testing such a guard?

Daphie answered 5/9, 2018 at 13:4 Comment(0)
L
15

The value PLATFORM_BROWSER_ID is not part of the public API, that's why it's importing it from a deep path, this is not allowed. Instead you could just put 'browser':

{ provide: PLATFORM_ID, useValue: 'browser' },

For any other platform, you can use these values:

export const PLATFORM_BROWSER_ID = 'browser';
export const PLATFORM_SERVER_ID = 'server';
export const PLATFORM_WORKER_APP_ID = 'browserWorkerApp';
export const PLATFORM_WORKER_UI_ID = 'browserWorkerUi';
Latrell answered 5/9, 2018 at 13:11 Comment(2)
This is stupid nevertheless it works... isPlatformBrowser method should receive an Object but mocking with a string is just fine.Chaille
Nice. I ran into this issue in a guard unit test also, and ended up wrapping my configureTestingModule code into a configure() function that takes the platform ID as a parameter and uses it in the provide. The configure() function is called by beforeEach code in a describe block that has SSR test, and another describe block with normal browser tests.Iggy
A
0

Another way to do the test could be to mock the result of isPlatformBrowser. For doing that you should create a wrapper of that function:

export class UniversalShellGuard implements CanActivate {
    ...
    private isBrowser(): boolean {
        return isPlatformBrowser(this.platformId);
    }
}

And then, using the jasmine Spy, you can mock the returned value of your isBrowser() method:

describe('UniversalShellGuard', () => {
    let guard: UniversalShellGuard;
  
    beforeEach(() => {
      TestBed.configureTestingModule({
        providers: [
          UniversalShellGuard,
          { provide: PLATFORM_ID, useValue: '' },
        ],
      });
  
      guard = TestBed.get(UniversalShellGuard);
    });
  
    it('should deny', () => {
      spyOn(guard as any, 'isBrowser').and.returnValue(true);
      expect(guard.canActivate()).toBe(false);
    });
});
Amagasaki answered 4/12, 2018 at 22:9 Comment(1)
isBrowser is a private method, you'll get an error in TS code, because you cannot access private methods and spy on themDunham

© 2022 - 2024 — McMap. All rights reserved.