[Documentation] Add a guide for using Page Objects Pattern with Jest
See original GitHub issue🚀 Feature Proposal
Page Objects Pattern has makes it much easier for developers to write tests that make assertions against the DOM. With a good Page Objects implementation, developers can create convenient interfaces between the DOM and their test suite. By abstracting references to specific DOM selectors, developers maintaining large test suites can make changes to selectors in one place instead of having to update every test for a component that changed.
Page Objects are especially useful when components are distributed as a collection of common components and meant to be consumed to build specialized components. For common component libraries, Page Objects allow the original developers to provide Page Objects to be used in tests of components that consume their components. By providing an interface to components’ DOM, the creators of the common components get the freedom to change the structure or selectors used to access DOM elements without necessarily breaking the tests that rely on these components.
Motivation
The motivation for this change it is to inform Jest users of a pattern that they can use to make their test suite less fragile, faster to write, more expressive and easier to maintain.
Example
No specific change is necessary support Page Objects in tests. It’s purely a documentation feature meant to inform developers of a pattern that they can use with Jest. I will show how Page Objects can be created with @bigtest/interactor
package. There are other libraries available for this purpose but @bigtest/interactor
is a great match for Jest because composability of these Page Objects is well suited for component libraries.
To understand how Page Objects work, let’s create a simple button component that will be with other components. We’ll start by creating the button component and a corresponding Page Object.
import React from 'react';
export default function Button({ children, classNames, onClick }) {
return <button className={['common-button', ...classNames]} onClick={onClick}>{children}</button>
}
To create a Page Object, we’ll create a module that will contain our page object. This file can be included in the component module, but this is a purely stylistic decision.
import { interactor, text, clickable } from '@bigtest/interactor'
export default interactor(
class ButtonInteractor {
static defaultScope = '.common-button' // this provides default selector for this button, it can be overwritten for specific tests
text = text() // provides text of the interactor
click = clickable() // allows to click the element
}
)
To use this interactor in a test, it’d look something like this. I’m going to use @bigtest/react
to render the actual component, but it’s not necessary. You can use any mechanism as long as your component is rendered before you start using the page object.
import Button from './button';
import ButtonInteractor from './button-interactor';
import { mount } from '@bigtest/react';
describe('<Button />', () => {
let button = new ButtonInteractor();
it('is rendered', async () => {
await mount(() => <Button />)
// interactors provide some built in properties that are provided for free because you're using the interactor
expect(button.isPresent).toBe(true);
});
it('renders provided text', async () => {
await mount(() => <Button>hello world</Button>)
expect(button.text).toBe('hello world');
});
it('calls onClick handler', async () => {
const handler = jest.fn();
await mount(() => <Button onClick={handler}>hello world</Button>)
await button.click();
expect(handler).toBeCalled();
});
})
When this component is used in another component, the developer using the provided component can import the page object and compose it with their own to test their component. Let’s create a component that uses the button and test it.
import { Button } from 'common-components';
export default function ContactForm({ onSubmit, onChangeName, name }) {
return (
<form class="contact-form" onSubmit={onSubmit}>
<input classNames="first-name" value={name} onChange={e => onChangeName(e.target.value) } />
<Button classNames="submit-button" type="submit">Submit</button>
</div>
)
}
To test this form component,
import { Button, ButtonIteractor } from 'common-components';
import { mount } from '@bigtest/react';
import { interactor, fillable, scoped } from '@bigtest/interactor';
const ContactFormInteractor = interactor(
class {
static defaultScope = 'contact-form';
enterFirstName = fillable('input'); // this will be scoped to the form's default scope
submitButton = scoped('.submit-button', ButtonInteractor) // mount composed interactor
}
)
describe('<ContactForm />', () => {
const form = new ContactFormInteractor();
it('allows user to input text', async () => {
const handler = jest.fn();
await mount(() => <ContactForm onChangeName={handler} />);
await form.enterFirstName('Bob');
expect(handler).toBeCalledWith('Bob');
});
it('calls submit when button is pressed', () => {
const handler = jest.fn();
await mount(() => <ContactForm onSubmit={handler} />)
await form.submitButton.click();
expect(handler).toBeCalled();
});
});
This is a very simple example, but you can see how the button page object is composed into the form page object. The test is very clear about what is happening and the developer doesn’t need to work directly with DOM elements.
For an example of a robust component library using this pattern with @bigtest/interactor
you can checkout Folio Stripes component library used to build future platform for libraries around the world.
Pitch
Without making any changes to Jest, users of Jest can start using a pattern that is proven to scale to very large projects. This pattern makes writing DOM tests a joy.
Additional Resources
- Jest + BigTest Interactor = Component Test ❤️
@bigtest/interactor
github repo - https://github.com/bigtestjs/interactor@bigtest/interactor
documentation - https://www.bigtestjs.io/guides/interactors/introduction/@bigtest/react
github repo - https://github.com/bigtestjs/react
I’ll be happy to write this documentation if this addition is welcomed.
Issue Analytics
- State:
- Created 5 years ago
- Comments:7 (2 by maintainers)
Hey @taras, thanks for submitting! This looks like a really cool way to test components
What would you say to someone who would think that this guide would be used as just a marketing channel to funnel people to bigtestjs/interactor?
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.