モック
テストを書いているとき、内部または外部サービスの「偽の」バージョンを作成する必要が出てくるのは時間の問題です。これは一般的にモックと呼ばれています。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
完全な例はComponent Testing Exampleリポジトリにあります。
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はここで軽量なJest互換のスパイ実装である@vitest/spy
を再エクスポートしており、これはWebdriverIOのexpect
マッチャーで使用できます。これらのモック関数の詳細なドキュメントはVitestプロジェクトページで見つけることができます。
もちろん、ブラウザ環境をサポートしている限り、他のスパイフレームワーク(例:SinonJS)をインストールしてインポートすることもできます。
モジュール
ローカルモジュールをモックしたり、他のコードで呼び出される第三者ライブラリを観察したりして、引数、出力を検証したり、実装を再宣言したりすることができます。
関数をモックする方法は2つあります:テストコードで使用するモック関数を作成するか、モジュールの依存関係をオーバーライドするマニュアルモックを書くかです。
ファイルインポートのモック
コンポーネントが、クリックを処理するためのユーティリティメソッ ドをファイルからインポートしていると想像してみましょう。
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';