Web-Automation Testing
目录
Web - Automation Testing
Hints
- Make your test FAIL AT BEGINNING. Otherwise it may not work properly as you thought.
- (For UI Test) Test the feature like a user instead of a developer. We don’t need to test the implementation details.
- Only write test for current layer, do not drill down, we can create another test case to cover deeper layers/components
- Do not include more dependencies if you can do it with
Vanilla JS+Jest+Mock - All unit tests should be able to run WITHOUT NETWORK. Disable your network and run it to make sure no request sent.
- You can print log in HTTP module to check if any request are sent
- Run unit tests in PR pipeline and run integration tests (+E2E tests) on deploy pipeline.
- Modularization: Input and output should be clear for a module, which could be more testing-friendly
- Try to use hardcoded string on testing, if the constant variables changed, the test case will find the change and notify you
- Try to write new feature together with test case (or follow the TDD pattern from start of your project)
Best Practice
Commands
| Purpose | Command | Comment |
|---|---|---|
| Run single test file | npx jest src\folder\yourComponent1.test.js | Find the file first |
| Run single test case by case name (Recommended) | npx jest -t='keyword' --watch | Provide a keyword and run test with keyword |
Debug
screen.debug():- Prints the HTML to CLI, usually it will only print part of the content, you can set the env var to show more:
DEBUG_PRINT_LIMIT=20000. Or you can set the env var in test cases:process.env.DEBUG_PRINT_LIMIT = 20000;
- Prints the HTML to CLI, usually it will only print part of the content, you can set the env var to show more:
screen.logTestingPlaygroundURL():- Generate an URL powered by testing playground, you can open the url within browser.
- Note that theme provider may not work
it.only/describe.onlyto run only single test case/suite
UI Testing
https://kentcdodds.com/blog/common-mistakes-with-react-testing-library
Wait For Appearance
Usually used for feature with animation or loading indicator.
WaitFormust return anboolean- Remember to add
awaitbeforeWaitFor, otherwise it may not work - Animation can be tests with
waitFor + timeout
RTL
- Testing Library Query Priority
- Types of Queries
- Appearance and Disappearance
- Use title case for test ID (
data-testid="This-Is-A-Test-Id")
JEST
jest.mockwill be hoist to the top so mocking is used for whole file. We can usejest.spyOnto mock the implementation on each test case.jest.mockwill ignore the original behaviour butjest.spyOnwon’t. We can use both to check if certain function have been called.- Put
spyOnon the top of each test case, especially before therender() - When create new test:
- Check if we can mock the input or implementation
- Is this module too big? Can we split it into smaller modules and write test for each?
Dev - Jest Cheatsheet
Manual Mocks
- Mocking user modules:
__mocks__/module.jssubdirectory immediately adjacent to the module- Add
jest.mock('./moduleName')at top of the test
- Mocking node modules:
__mocks__/module.jsat ROOT path- NO need to call
jest.mockexplicitly, it will be mocked automatically
Partially Mock
https://jestjs.io/docs/mock-functions#mocking-partials
SpyOnwould be more convenient to do the mock.
Spy and Mock
/* import all */
import * as RequestUtils from 'src/utils';
/* DO NOT DO THIS */
// import RequestUtils from 'src/utils';
jest.spyOn(RequestUtils, 'requestUtil').mockImplementationOnce(() => {
return Promise.reject('mocked error');
})
Another example:
import { createChannel, createClient } from "nice-grpc";
const foo = { createChannel, createClient };
jest.spyOn(foo, "createClient").mockReturnValue({});
jest.spyOn(foo, "createChannel").mockReturnValue({});
React Testing Library
Basic Test
import React from "react";
import { render } from "@testing-library/react";
import { FixedButton } from "src/components/button";
describe("Button Component", () => {
it("should renders correctly", () => {
const { container } = render(<FixedButton />);
expect(container).not.toBeUndefined();
expect(container).toMatchSnapshot();
});
});
Nested Query
const title = await findByRole('heading', { name: /search results/i });
expect(await title).toBeVisible();
within(title).findByRole('progressbar');
(?) Mock a React Hook
import { useData } from "src/hooks";
/* required to set mockdata into a variable with name pattern "mock*" */
const mockData = {
data: 123456
}
jest.mock("src/hooks", () => ({
useData: jest.fn(),
}));
describe('Test for the mock data', () => {
it('should return mocked data', () => {
(useData as any).mockReturnValue(mockData);
/* assertions */
})
})
Mock Component
Named Export
jest.mock(
"src/NativeCommentBox",
() => ({
/* make sure for the correct export name */
NativeCommentContainer: () => (
<span data-testid={nativeCommentContainerTestId}>children</span>
),
})
);
Default Export
jest.mock("react-markdown", () => ({
default: () => <>children</>,
}));
Troubleshoot
Test case are skiped
Delete .only()
State are shared between tests
Similar Scenario:
- Testing are fail on second test.
- Testing fails randomly
Potentially you are import the component with providers.
Try to write a renderer function with isolated providers.
export const renderWithState = (
component,
{ initialState, ...renderOptions } = {}
) => {
const initialStore = getInitialStore(initialState);
const Wrapper = ({ children }) => {
return (
<StoreProvider store={initialStore}>
<OtherProvider>
{children}
</OtherProvider>
</StoreProvider>
);
};
return render(component, { wrapper: Wrapper, ...renderOptions });
};
Important Note: Custom renderer with Providers
const { container } = renderer(
<MyComponent />
);
expect(container).not.toBeNull(); /* This works */
const { container, getByRole } = renderWithState( /* renderWithState may render 'MyComponent' with Providers */
<MyComponent />
);
/* Potentially this will be always true because we have wrap 'MyComponent' with Providers */
expect(container).not.toBeNull();
/* Try to do other assertions on the elements within container instead of just check container */
Jest Hints
jest.mock() hoists mocks to the top of your module’s scope
TypeError: Expected container to be an Element, a Document or a DocumentFragment but got string
import {
fireEvent,
queryByRole, /* DO NOT DO THIS */
render,
waitFor
} from '@testing-library/react';
const {
container, queryByRole /* DO THIS */
} = render(<App />);
data-testid shows in React Dev Tools but not in Element Tab
Prevent to put the data-testid to custom components.
Otherwise we need to pass the data-testid to children components explicitly.
ß
const Panel = ({
attr1,
attr2
...rest
}) => {
return (
<PanelHeader
attr1={attr1}
attr2={attr2}
{...rest}
>
)
}
When testing, code that causes React state updates should be wrapped into act(…)
https://davidwcai.medium.com/react-testing-library-and-the-not-wrapped-in-act-errors-491a5629193b
Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one.
This is usually same request happend in same period.
Check if you forget to add ‘await’ before find* or waitFor
Jest.SpyOn.Mock doesn’t work
SpyOn and should write before the render.