How to mock window.screen.width in Angular Unit Test with Jasmine

Mocking Angular Material BreakpointObserver

I'm guessing you don't really want to mock window.screen, you actually want to mock BreakpointObserver. After all, no need to test their code, you just want to test that your code responds properly to the observable returned by BreakpointObserver.observe() with different screen sizes.

There are a lot of different ways to do this. To illustrate one method, I put together a STACKBLITZ with your code showing how I would approach this. Things to note that differ from what your code is above:

  • Your code sets up the observables in the constructor. Because of this the mock has to be changed BEFORE the service is instantiated, so you will see the call to resize() happens before the service = TestBed.get(MyService); call.
  • I mocked BreakpointObserver with a spyObj, and called a fake function in place of the BreakpointObserver.observe() method. This fake function uses a filter I had set up with the results I wanted from the various matches. They all started as false, because the values would change depending on what screen size is desired to be mocked, and that is set up by the resize() function you were using in the code above.

Note: there are certainly other ways to approach this. Check out the angular material's own breakpoints-observer.spec.ts on github. This is a much nicer general approach than what I outline here, which was just to test the function you provided.

Here is a snip from the StackBlitz of the new suggested describe function:

describe(MyService.name, () => {
  let service: MyService;
  const matchObj = [
    // initially all are false
    { matchStr: '(min-width: 1024px)', result: false },
    { matchStr: '(min-width: 1366px)', result: false },
    { matchStr: '(max-width: 1366px)', result: false },
  ];
  const fakeObserve = (s: string[]): Observable<BreakpointState> =>
    from(matchObj).pipe(
      filter(match => match.matchStr === s[0]),
      map(match => ({ matches: match.result, breakpoints: {} })),
    );
  const bpSpy = jasmine.createSpyObj('BreakpointObserver', ['observe']);
  bpSpy.observe.and.callFake(fakeObserve);
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [],
      providers: [MyService, { provide: BreakpointObserver, useValue: bpSpy }],
    });
  });

  it('should be createable', () => {
    service = TestBed.inject(MyService);
    expect(service).toBeTruthy();
  });

  describe('observe()', () => {
    function resize(width: number): void {
      matchObj[0].result = width >= 1024;
      matchObj[1].result = width >= 1366;
      matchObj[2].result = width <= 1366;
    }

    it('should return Observable<SidebarMode>', () => {
      resize(1000);
      service = TestBed.inject(MyService);
      service.observe().subscribe(mode => {
        expect(
          Object.values(SidebarMode).includes(SidebarMode[mode]),
        ).toBeTruthy();
      });
    });

    it('should return SidebarMode.Closed', () => {
      resize(600);
      service = TestBed.inject(MyService);
      service
        .observe()
        .subscribe(mode => expect(mode).toBe(SidebarMode.Closed));
    });

    it('should return SidebarMode.Minified', () => {
      resize(1200);
      service = TestBed.inject(MyService);
      service
        .observe()
        .subscribe(mode => expect(mode).toBe(SidebarMode.Minified));
    });

    it('should return SidebarMode.Open', () => {
      resize(2000);
      service = TestBed.inject(MyService);
      service.observe().subscribe(mode => expect(mode).toBe(SidebarMode.Open));
    });
  });
});

I'm guessing the BreakPointObserver listen to the resize event so maybe you could try something like mocking the window.innerWidth / window.outerWidth with jasmine ?

spyOnProperty(window, 'innerWidth').and.returnValue(760);

Then you manually trigger a resize event :

window.dispatchEvent(new Event('resize'));

It would look like that :

    it('should mock window inner width', () => {
        spyOnProperty(window, 'innerWidth').and.returnValue(760);
        window.dispatchEvent(new Event('resize'));
    });