模拟
当编写测试时,迟早你会需要创建内部或外部服务的"假"版本。这通常被称为模拟(mocking)。WebdriverIO提供实用函数来帮助你。你可以通过import { fn, spyOn, mock, unmock } from '@wdio/browser-runner'
来访问它。有关可用模拟工具的更多信息,请参阅API文档。
函数
为了验 证某些函数处理程序是否作为组件测试的一部分被调用,@wdio/browser-runner
模块导出了你可以用来测试这些函数是否被调用的模拟原语。你可以通过以下方式导入这些方法:
import { fn, spyOn } from '@wdio/browser-runner'
通过导入fn
,你可以创建一个间谍函数(模拟)来跟踪其执行情况,而使用spyOn
则可以跟踪已创建对象上的方法。
- Mocks
- Spies
完整示例可在组件测试示例仓库中找到。
import React from 'react'
import { $, expect } from '@wdio/globals'
import { fn } from '@wdio/browser-runner'
import { Key } from 'webdriverio'
import { render } from '@testing-library/react'
import LoginForm from '../components/LoginForm'
describe('LoginForm', () => {
it('should call onLogin handler if username and password was provided', async () => {
const onLogin = fn()
render(<LoginForm onLogin={onLogin} />)
await $('input[name="username"]').setValue('testuser123')
await $('input[name="password"]').setValue('s3cret')
await browser.keys(Key.Enter)
/**
* verify the handler was called
*/
expect(onLogin).toBeCalledTimes(1)
expect(onLogin).toBeCalledWith(expect.equal({
username: 'testuser123',
password: 's3cret'
}))
})
})
完整示例可在examples目录中找到。
import { expect, $ } from '@wdio/globals'
import { spyOn } from '@wdio/browser-runner'
import { html, render } from 'lit'
import { SimpleGreeting } from './components/LitComponent.ts'
const getQuestionFn = spyOn(SimpleGreeting.prototype, 'getQuestion')
describe('Lit Component testing', () => {
it('should render component', async () => {
render(
html`<simple-greeting name="WebdriverIO" />`,
document.body
)
const innerElem = await $('simple-greeting').$('p')
expect(await innerElem.getText()).toBe('Hello, WebdriverIO! How are you today?')
})
it('should render with mocked component function', async () => {
getQuestionFn.mockReturnValue('Does this work?')
render(
html`<simple-greeting name="WebdriverIO" />`,
document.body
)
const innerElem = await $('simple-greeting').$('p')
expect(await innerElem.getText()).toBe('Hello, WebdriverIO! Does this work?')
})
})
WebdriverIO在这里只是重新导出了@vitest/spy
,这是一个轻量级的Jest兼容间谍实现,可以与WebdriverIO的expect
匹配器一起使用。你可以在Vitest项目页面上找到关于这些模拟函数的更多文档。
当然,你也可以安装和导入任何其他间谍框架,例如SinonJS,只要它支持浏览器环境。
模块
模拟本地模块或观察在其他代码中调用的第三方库,允许你测试参数、输出,甚至重新声明其实现。
有两种方法可以模拟函数:一种是创建一个在测试代码中使用的模拟函数,另一种是编写手动模拟来覆盖模块依赖项。
模拟文件导入
让我们想象我们的组件正在从一个文件导入一个实用方法来处理点击。
export function handleClick () {
// handler implementation
}
在我们的组件中,点击处理程序的使用如下:
import { handleClick } from './utils.js'
@customElement('simple-button')
export class SimpleButton extends LitElement {
render() {
return html`<button @click="${handleClick}">Click me!</button>`
}
}
要模拟来自utils.js
的handleClick
,我们可以在测试中使用mock
方法,如下所示:
import { expect, $ } from '@wdio/globals'
import { mock, fn } from '@wdio/browser-runner'
import { html, render } from 'lit'
import { SimpleButton } from './LitComponent.ts'
import { handleClick } from './utils.js'
/**
* mock named export "handleClick" of `utils.ts` file
*/
mock('./utils.ts', () => ({
handleClick: fn()
}))
describe('Simple Button Component Test', () => {
it('call click handler', async () => {
render(html`<simple-button />`, document.body)
await $('simple-button').$('button').click()
expect(handleClick).toHaveBeenCalledTimes(1)
})
})
模拟依赖项
假设我们有一个从API获取用户的类。该类使用axios
调用API,然后返回包含所有用户的data属性:
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data)
}
}
export default Users
现在,为了测试这个方法而不实际调用API(从而创建缓慢和脆弱的测试),我们可以使用mock(...)
函数自动模拟axios模块。
一旦我们模拟了该模块,我们可以为.get
提供一个mockResolvedValue
,返回我们希望测试断言的数据。实际上,我们是在说我们希望axios.get('/users.json')
返回一个假的响应。
import axios from 'axios'; // imports defined mock
import { mock, fn } from '@wdio/browser-runner'
import Users from './users.js'
/**
* mock default export of `axios` dependency
*/
mock('axios', () => ({
default: {
get: fn()
}
}))
describe('User API', () => {
it('should fetch users', async () => {
const users = [{name: 'Bob'}]
const resp = {data: users}
axios.get.mockResolvedValue(resp)
// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))
const data = await Users.all()
expect(data).toEqual(users)
})
})
部分模拟
模块的子集可以被模拟,而模块的其余部分可以保持其实际实现:
export const foo = 'foo';
export const bar = () => 'bar';
export default () => 'baz';