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

血红的双手。 提交于 2020-01-14 13:51:09

问题


I have a BreakpointService, which tells me - depending on the screen width - in which SidebarMode (closed - minified - open) I should display my Sidebar.

This is the main part of the service:

constructor(private breakpointObserver: BreakpointObserver) {
    this.closed$ = this.breakpointObserver.observe(['(min-width: 1024px)']).pipe(
      filter((state: BreakpointState) => !state.matches),
      mapTo(SidebarMode.Closed)
    );

    this.opened$ = this.breakpointObserver.observe(['(min-width: 1366px)']).pipe(
      filter((state: BreakpointState) => state.matches),
      mapTo(SidebarMode.Open)
    );

    const minifiedStart$: Observable<boolean> = this.breakpointObserver.observe(['(min-width: 1024px)']).pipe(map(state => state.matches));

    const minifiedEnd$: Observable<boolean> = this.breakpointObserver.observe(['(max-width: 1366px)']).pipe(map(state => state.matches));

    this.minified$ = minifiedStart$.pipe(
      flatMap(start => minifiedEnd$.pipe(map(end => start && end))),
      distinctUntilChanged(),
      filter(val => val === true),
      mapTo(SidebarMode.Minified)
    );

    this.observer$ = merge(this.closed$, this.minified$, this.opened$);
  }

with this line I can subscribe to the events:

this.breakpointService.observe().subscribe();

Now, I would like to test the different modes within a unit test, but I don't know

how to mock the window.screen.width property within a test

I tried several things, but nothing worked out for me.

This is my test-setup so far:

describe('observe()', () => {
    function resize(width: number): void {
      // did not work
      // window.resizeTo(width, window.innerHeight);
      // (<any>window).screen = { width: 700 };
      // spyOn(window, 'screen').and.returnValue(...)
    }

    let currentMode;
    beforeAll(() => {
      service.observe().subscribe(mode => (currentMode = mode));
    });

    it('should return Observable<SidebarMode>', async () => {
      resize(1000);

      expect(Object.values(SidebarMode).includes(SidebarMode[currentMode])).toBeTruthy();
    });

    xit('should return SidebarMode.Closed', async () => {
      resize(600);

      expect(currentMode).toBe(SidebarMode.Closed);
    });

    xit('should return SidebarMode.Minified', async () => {
      resize(1200);

      expect(currentMode).toBe(SidebarMode.Minified);
    });

    xit('should return SidebarMode.Open', async () => {
      resize(2000);

      expect(currentMode).toBe(SidebarMode.Open);
    });
  });

回答1:


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', () => {
    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 => <BreakpointState>{ 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.get(MyService);
        expect(service).toBeTruthy();
    });

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

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

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

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

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

I hope this helps




回答2:


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'));
    });


来源:https://stackoverflow.com/questions/53204907/how-to-mock-window-screen-width-in-angular-unit-test-with-jasmine

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!