如何测试是否调用了 useState 或者是否点击了按钮?
How to test whether useState has been called or a button has been clicked?
我一直在努力寻找一种方法来在一个简单的 tsx
文件上获得 100% 的覆盖率,而唯一开玩笑地告诉我需要覆盖率的两行是我传递执行 setState
来自 useState
.
我读到我不应该测试挂钩,因为用户只关心 UI。这就是为什么我的问题是如何测试这两条线。
function App(): JSX.Element {
const [step, setStep] = React.useState(0);
return (
<main className={style.app}>
<Grid className={style.header} centerAlign>
<button
id="home-button"
className={style.headerHomeBtn}
onClick={() => setStep(0)}
>
<img className={style.headerHome} src={home} alt="home button" />
</button>
<h1>Financial Advisor</h1>
</Grid>
{step === 0 && <RiskSelector continue={() => setStep(1)} />}
{step === 1 && <Portfolio />}
</main>
);
}
Uncovered line #s: 18-24
哪些是 onClick
和 continue
道具。到目前为止我的测试是:
describe('Dummy', () => {
let wrapper: any;
const setState = jest.fn();
const useStateMock: any = (initState: any) => [initState, setState];
beforeEach(() => {
wrapper = shallow(<Dummy />);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('Home button clicked', () => {
it('calls setStep with 0', () => {
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
console.log(wrapper.debug());
wrapper.find('button').props().onClick();
expect(setState).toHaveBeenCalled();
});
});
});
控制台输出只显示按钮在那里。
测试未通过,因为从未调用过 setState
。
不是只有用户只关心UI,功能组件的测试也只关心UI :)。换句话说,测试不断变化的呈现内容,而不是功能的实现。
因此,模拟该按钮单击并测试是否呈现 RiskSelector
或 Portfolio
。
您不应模拟 React.useState()
挂钩,并测试事件处理程序的实现细节。您应该测试组件的行为。例如,当状态发生变化时,视图会发生什么。
例如
App.tsx
:
import { Grid } from './Grid';
import React from 'react';
import { RiskSelector } from './RiskSelector';
import { Portfolio } from './Portfolio';
const home = 'http://localhost:3000/home.png';
export function App(): JSX.Element {
const [step, setStep] = React.useState(0);
return (
<main>
<Grid centerAlign>
<button id="home-button" onClick={() => setStep(0)}>
<img src={home} alt="home button" />
</button>
<h1>Financial Advisor</h1>
</Grid>
{step === 0 && <RiskSelector continue={() => setStep(1)} />}
{step === 1 && <Portfolio />}
</main>
);
}
Grid.tsx
:
import React from 'react';
export function Grid({ children, centerAlign }) {
return <div>{children}</div>;
}
Portfolio.tsx
:
import React from 'react';
export function Portfolio() {
return <div>Portfolio</div>;
}
RiskSelector.tsx
:
import React from 'react';
export function RiskSelector({ continue: onContinue }) {
return <div onClick={onContinue}></div>;
}
App.test.tsx
:
import React from 'react';
import { App } from './App';
import { shallow } from 'enzyme';
import { Portfolio } from './Portfolio';
import { RiskSelector } from './RiskSelector';
describe('67412919', () => {
let wrapper: any;
beforeEach(() => {
wrapper = shallow(<App />);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('Home button clicked', () => {
it('calls setStep with 0', () => {
wrapper.find('button').simulate('click');
expect(wrapper.find(RiskSelector)).toHaveLength(1);
});
it('calls setStep with 1', () => {
wrapper.find(RiskSelector).invoke('continue')();
expect(wrapper.find(Portfolio)).toHaveLength(1);
expect(wrapper.find(RiskSelector)).toHaveLength(0);
});
});
});
App.tsx
文件的 100% 覆盖率的单元测试结果。
PASS examples/67412919/App.test.tsx (7.433 s)
67412919
Home button clicked
✓ calls setStep with 0 (24 ms)
✓ calls setStep with 1 (12 ms)
------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------------|---------|----------|---------|---------|-------------------
All files | 84.21 | 100 | 50 | 84.21 |
App.tsx | 100 | 100 | 100 | 100 |
Grid.tsx | 66.67 | 100 | 0 | 66.67 | 4
Portfolio.tsx | 66.67 | 100 | 0 | 66.67 | 3
RiskSelector.tsx | 66.67 | 100 | 0 | 66.67 | 4
------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 7.958 s, estimated 8 s
我一直在努力寻找一种方法来在一个简单的 tsx
文件上获得 100% 的覆盖率,而唯一开玩笑地告诉我需要覆盖率的两行是我传递执行 setState
来自 useState
.
我读到我不应该测试挂钩,因为用户只关心 UI。这就是为什么我的问题是如何测试这两条线。
function App(): JSX.Element {
const [step, setStep] = React.useState(0);
return (
<main className={style.app}>
<Grid className={style.header} centerAlign>
<button
id="home-button"
className={style.headerHomeBtn}
onClick={() => setStep(0)}
>
<img className={style.headerHome} src={home} alt="home button" />
</button>
<h1>Financial Advisor</h1>
</Grid>
{step === 0 && <RiskSelector continue={() => setStep(1)} />}
{step === 1 && <Portfolio />}
</main>
);
}
Uncovered line #s: 18-24
哪些是 onClick
和 continue
道具。到目前为止我的测试是:
describe('Dummy', () => {
let wrapper: any;
const setState = jest.fn();
const useStateMock: any = (initState: any) => [initState, setState];
beforeEach(() => {
wrapper = shallow(<Dummy />);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('Home button clicked', () => {
it('calls setStep with 0', () => {
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
console.log(wrapper.debug());
wrapper.find('button').props().onClick();
expect(setState).toHaveBeenCalled();
});
});
});
控制台输出只显示按钮在那里。
测试未通过,因为从未调用过 setState
。
不是只有用户只关心UI,功能组件的测试也只关心UI :)。换句话说,测试不断变化的呈现内容,而不是功能的实现。
因此,模拟该按钮单击并测试是否呈现 RiskSelector
或 Portfolio
。
您不应模拟 React.useState()
挂钩,并测试事件处理程序的实现细节。您应该测试组件的行为。例如,当状态发生变化时,视图会发生什么。
例如
App.tsx
:
import { Grid } from './Grid';
import React from 'react';
import { RiskSelector } from './RiskSelector';
import { Portfolio } from './Portfolio';
const home = 'http://localhost:3000/home.png';
export function App(): JSX.Element {
const [step, setStep] = React.useState(0);
return (
<main>
<Grid centerAlign>
<button id="home-button" onClick={() => setStep(0)}>
<img src={home} alt="home button" />
</button>
<h1>Financial Advisor</h1>
</Grid>
{step === 0 && <RiskSelector continue={() => setStep(1)} />}
{step === 1 && <Portfolio />}
</main>
);
}
Grid.tsx
:
import React from 'react';
export function Grid({ children, centerAlign }) {
return <div>{children}</div>;
}
Portfolio.tsx
:
import React from 'react';
export function Portfolio() {
return <div>Portfolio</div>;
}
RiskSelector.tsx
:
import React from 'react';
export function RiskSelector({ continue: onContinue }) {
return <div onClick={onContinue}></div>;
}
App.test.tsx
:
import React from 'react';
import { App } from './App';
import { shallow } from 'enzyme';
import { Portfolio } from './Portfolio';
import { RiskSelector } from './RiskSelector';
describe('67412919', () => {
let wrapper: any;
beforeEach(() => {
wrapper = shallow(<App />);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('Home button clicked', () => {
it('calls setStep with 0', () => {
wrapper.find('button').simulate('click');
expect(wrapper.find(RiskSelector)).toHaveLength(1);
});
it('calls setStep with 1', () => {
wrapper.find(RiskSelector).invoke('continue')();
expect(wrapper.find(Portfolio)).toHaveLength(1);
expect(wrapper.find(RiskSelector)).toHaveLength(0);
});
});
});
App.tsx
文件的 100% 覆盖率的单元测试结果。
PASS examples/67412919/App.test.tsx (7.433 s)
67412919
Home button clicked
✓ calls setStep with 0 (24 ms)
✓ calls setStep with 1 (12 ms)
------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------------|---------|----------|---------|---------|-------------------
All files | 84.21 | 100 | 50 | 84.21 |
App.tsx | 100 | 100 | 100 | 100 |
Grid.tsx | 66.67 | 100 | 0 | 66.67 | 4
Portfolio.tsx | 66.67 | 100 | 0 | 66.67 | 3
RiskSelector.tsx | 66.67 | 100 | 0 | 66.67 | 4
------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 7.958 s, estimated 8 s