Mocking
When writing tests it's only a matter of time before you need to create a "fake" version of an internal — or external — service. This is commonly referred to as mocking. WebdriverIO provides utility functions to help you out. You can import { fn, spyOn, mock, unmock } from '@wdio/browser-runner'
to access it. See more information about the available mocking utilities in the API docs.
Functions
In order to validate whether certain function handler are called as part of your component tests, the @wdio/browser-runner
module exports mocking primitives you can use to test, if these functions have been called. You can import these methods via:
import { fn, spy } from '@wdio/browser-runner'
By importing fn
you can create a spy function (mock) to track its execution and with spyOn
track a method on an already created object.
- Mocks
- Spies
The full example can be found in the Component Testing Example repository.
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'
}))
})
})
The full example can be found in the examples directory.
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 just re-exports @vitest/spy
here which is a lightweight Jest compatible spy implementation that can be used with WebdriverIOs expect
matchers. You can find more documentation on these mock functions on the Vitest project page.
Of course, you can also install and import any other spy framework, e.g. SinonJS, as long as it supports the browser environment.
Modules
Mock local modules or observe third-party-libraries, that are invoked in some other code, allowing you to test arguments, output or even redeclare its implementation.
There are two ways to mock functions: Either by creating a mock function to use in test code, or writing a manual mock to override a module dependency.
Mocking File Imports
Let's imagine our component is importing a utility method from a file to handle a click.
export function handleClick () {
// handler implementation
}
In our component the click handler is used as following:
import { handleClick } from './utils.js'
@customElement('simple-button')
export class SimpleButton extends LitElement {
render() {
return html`<button @click="${handleClick}">Click me!</button>`
}
}
To mock the handleClick
from utils.js
we can use the mock
method in our test as following:
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)
})
})