目录

Window 对象的各种测试方法总结

前言

在平时前端项目开发中有很多需要对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 公司无任何关系。


https://cdn.staticaly.com/gh/guzhongren/data-hosting@master/20210819/wechat.ae9zxgscqcg.png