问题
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 theservice = TestBed.get(MyService);
call. - I mocked
BreakpointObserver
with a spyObj, and called a fake function in place of theBreakpointObserver.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 theresize()
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