前言
在平时前端项目开发中有很多需要对window
对象进行进行操作,比如改变window.location.href
的值,在前端页面中,这会使浏览器发生页面跳转,还有如window.location.replace()
, 那么在测试中,虽然 jest 会有部分初始值,但有时候我们需要明确给定值,让测试更明确。
Window 对象测试分析
window 对象有属性 (property
) 和方法 (method
), 在此我们以href
, window.location.href
+ hash 和 postMessage
几个特性来测试, 对每个类型 (property/method) 将使用至少三种方法来展示,测试方法可以分为如下四类:
- delete - 在原对象 window 上进行测试
- Object.defineProperty - 重新定义属性
- mockfile - mock 整个被测文件
- spyOn - 对特定方法进行 mock
被测文件
在这可以不用遵循TDD
的流程,直接给出被测文件内容,也可在 github
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
export const changeHref = (value: string) => {
window.location.href = value
}
export const addHash = (hash: string): string => {
return window.location.href + `#${hash}`
}
export const sendMessage = (message) => {
window.top.postMessage({
type: 'message',
data: message,
}, '*' )
}
|
测试
property - changeHref
delete
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import { changeHref } from '../src/attribute'
describe('attribute', () => {
const { location } = window
beforeEach(() => {
delete window.location
})
afterEach(() => {
window.location = location
})
it('should change href to http://test.com when newURL is http://test.com', () => {
const newURL = "http://test.com"
window.location = {
...location,
href: ''
}
changeHref(newURL)
expect(window.location.href).toBe(newURL)
})
})
|
Object.defineProperty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import { changeHref } from '../src/attribute'
describe('attribute', () => {
let windowSpy;
beforeEach(() => {
windowSpy= jest.spyOn(window, 'location', 'get')
})
afterEach(() =>{
windowSpy.mockRestore()
})
it('jest.spyOn', () => {
expect(window.location.href).toBe('http://localhost/')
const newURL = "http://test.com"
windowSpy.mockImplementation(() => ({
href: ''
}))
changeHref(newURL)
expect(windowSpy).toHaveBeenCalled()
})
})
|
spyOn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import { changeHref } from '../src/attribute'
describe('attribute', () => {
let windowSpy;
beforeEach(() => {
windowSpy= jest.spyOn(window, 'location', 'get')
})
afterEach(() =>{
windowSpy.mockRestore()
})
it('jest.spyOn', () => {
expect(window.location.href).toBe('http://localhost/')
const newURL = "http://test.com"
windowSpy.mockImplementation(() => ({
href: ''
}))
changeHref(newURL)
expect(windowSpy).toHaveBeenCalled()
})
})
|
method - addHash
delete
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import { addHash } from '../src/attribute'
describe('method', () => {
const { location } = window
beforeEach(() => {
delete window.location;
window.location = {
...location,
href: 'http://href.com'
}
})
afterEach(() => {
window.location = location
})
it("should return http://href.com#123 when give 123", () => {
expect(addHash('123')).toEqual('http://href.com#123')
})
})
|
Object.defineProperty
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import { addHash } from '../src/attribute'
describe('method', () => {
const { location } = window
beforeEach(() => {
Object.defineProperty(window, 'location', {
value: {
...location,
href: 'http://href.com',
},
})
})
afterEach(() => {
Object.defineProperty(window, 'location', location)
})
it("should return http://href.com#123 when give 123", () => {
expect(addHash('123')).toEqual('http://href.com#123')
})
})
|
mockFile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import * as attribute from '../src/attribute'
jest.mock('../src/attribute', () => {
return {
__esModule: true,
addHash: jest.fn(),
};
});
beforeEach( () => {
jest.resetModules();
})
describe('method', () => {
it('mocks `addHash`', () => {
expect(jest.isMockFunction(attribute.addHash)).toBe(true);
});
it('verify method has been invoked', () => {
expect(attribute.addHash).not.toHaveBeenCalled();
// will failed
// expect(attribute.addHash('test')).toEqual('http://localhost/#test')
attribute.addHash('234')
expect(attribute.addHash).toHaveBeenCalled()
expect(attribute.addHash).toBeCalledTimes(1)
expect(attribute.addHash).toBeCalledWith('234')
})
})
|
spyOn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import { addHash } from '../src/attribute'
describe('method', () => {
let windowSpy
beforeEach(() => {
windowSpy = jest.spyOn(window, 'location', 'get')
})
afterEach(() => {
windowSpy.mockRestore()
})
it('mocks `addHash`', () => {
expect(jest.isMockFunction(windowSpy)).toBe(true)
});
it('spyOn for addHash', () => {
windowSpy.mockImplementation(() => ({
href: 'http://test.com',
}))
expect(windowSpy).not.toHaveBeenCalled()
expect(addHash('123')).toEqual('http://test.com#123')
expect(windowSpy).toHaveBeenCalled();
})
})
|
method - postMessage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import {sendMessage} from '../src/attribute'
describe('multiple', () => {
it('sendMessage test with multiple test method', () => {
Object.defineProperty(window, 'top', {
value: window,
writable: true,
enumerable: true,
configurable: true,
})
Object.defineProperty(window, 'postMessage', {
writable: true,
value: jest.fn(),
})
sendMessage('message')
expect(window.parent.postMessage).toHaveBeenCalled()
expect(window.parent.postMessage).toBeCalledTimes(1)
})
})
|
总结
总结就是整理自己,方便自己,如若能方便他人,那就是意外了。
源代码:https://github.com/AndorLab/test-window-object
Refs
Disclaimer
本文仅代表个人观点,与 Thoughtworks 公司无任何关系。