Skip to content

Test API usability improvements #55

@robertknight

Description

@robertknight

This issue tracks a few ideas for improving the APIs we use for writing tests. This means reducing the amount of code needed for common uses, eliminating pitfalls etc.

This issue is a place to brainstorm. Separate issues can be created to flesh ideas out more fully. Some of these issues are also really about the underlying libraries, so the issues need to be created downstream.

waitFor method on wrappers

A common action is to trigger an async operation in a test, then wait for the UI to change in some way.
This is currently handled via a waitForElement function, but it would be useful if it was an operation on the wrapper.

Current:

import { mount, waitForElement } from '@hypothesis/frontend-testing';
...
const wrapper = mount(<Widget/>);
const items = waitForElement(wrapper, 'ul[data-testid="item-list"]');

Proposed:

import { mount } from '@hypothesis/frontend-testing';
...
const wrapper = mount(<Widget/>);
const items = await wrapper.waitFor('ul[data-testid="item-list"]');

Clarify or simplify use of act and wrapper.update()

It is often not intuitive when act and wrapper.update() are required. It would make writing tests easier if there was at least good documentation, or perhaps better yet changes that could be made to reduce how often this is needed.

  • The act function runs a callback and then repeatedly flushes effects and state updates, until a flush triggers no further updates
  • wrapper.update() synchronizes the Enzyme wrapper's snapshot of the rendered output to match the current output

A common pattern of usage is:

act(() => {
  wrapper.find('SomeChild').prop('onClick')();
});
wrapper.update();

Here the act callback ensures that state updates and effects triggered by onClick are flushed, then wrapper.update ensures that subsequent queries on wrapper reflect the current UI.

Often it is possible to omit the use of act if the onClick handler triggers state updates but no effects. This is because the wrapper.update() call will implicitly flush any pending state updates. Internally wrapper.update() calls the Enzyme adapater's getNode method and the MountRenderer.getNode implementation in enzyme-adapter-preact-pure calls flushRenders first.

Inspecting updates to child components

Another common sequence looks like:

// Get child and trigger an update
let child = wrapper.find('SomeWidget');
assert.equal(child.prop('value'), 'Original value');
act(() => {
  child.prop('onChange')('Updated value');
}); // nb. The `act` can often be omitted. See above.

// Refresh the parent component and re-find the child.
wrapper.update();
child = wrapper.find('SomeWidget');
assert.equal(child.prop('value'), 'Updated value');

Re-finding the child is necessary because the original child value was a snapshot from the initial render that doesn't change after refreshing the root wrapper. It would be more ergonomic if the above could be written as:

const child = wrapper.find('SomeWidget');
assert.equal(child.prop('value'), 'Original value');
child.prop('onChange')('Updated value');
assert.equal(child.prop('value'), 'Updated value');

Built-in data-testid support

We extensively use the pattern of adding a data-testid attribute to DOM elements and components, then use an attribute selector to find those in tests. This pattern is taken from Testing Library. eg.

wrapper.find('button[data-testid="foo-button"]')

These selectors are a bit awkward to write, especially if foo-button is partly dynamic. It would be useful to have some built-in support for them. A complication is that the data-testid value is often not enough on its own, because that attribute might be passed down multiple elements. eg. <Button data-testid="foo">...</Button> will render both a Button and button with data-testid props.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions