function Counter()	{
const [count,	setCount] =	useState(0);
const increase	=	()	=> setCount(count + 1);
const decrease	=	()	=> setCount(count - 1);
return (
<div className="simple-counter">
<button className="dec" onClick={decrease}>-</button>
<span className="value">{count}</span>
<button className="inc" onClick={increase}>+</button>
</div>
);
}
it('생성시 버튼과 초기값을 렌더링한다.',	()	=> {
const div =	document.createElement('div');
ReactDOM.render(<Counter	/>,	div);
expect(diffableHTML(div.innerHTML)).toBe(
diffableHTML(`
<div	class="simple-counter">
<button	class="dec">-</button>
<span	class="value">0</span>
<button	class="inc">+</button>
</div>
`)
);
});
it('생성시 버튼과 초기값을 렌더링한다.',	()	=> {
const div =	document.createElement('div');
ReactDOM.render(<Counter	/>,	div);
expect(diffableHTML(div.innerHTML))
.toMatchSnapshot();
});
exports[`생성시 버튼과 초기값을 렌더링한다.	1`]	=	`
"
<div	class="simple-counter">
<button	class="dec">
-
</button>
<span	class="value">
0
</span>
<button	class="inc">
+
</button>
</div>
"
`;
const puppeteer =	require('puppeteer');
const {	percySnapshot }	=	require('@percy/puppeteer')
it('Visual	Testing',	async ()	=> {
const browser =	await puppeteer.launch();
const page =	await browser.newPage();
await page.goto('http://localhost:3000/');		
await percySnapshot(page,	'초기 상태');
const btnInc =	await page.$('button.inc');
await btnInc.click();
await percySnapshot(page,	'+	버튼 클릭 후');
await browser.close();
});
it('초기값은 0이다.',	()	=>	{
const value =	container.querySelector('.value');
expect(value.innerHTML).toBe('0');
});
it('+	버튼 클릭 시 1	증가한다.',	()	=> {
const btnInc =	container.querySelector('button.inc');
const value =	container.querySelector('.value');
btnInc.click();
expect(value.innerHTML).toBe('1');
});
container.querySelector('.box	>	.value');
container.querySelector('span.value');
container.querySelector('.text.white');
container.children[0];
container.querySelector('.count-value');
container.querySelector('[data-testid="count-value"]');
function Counter()	{
//	..
return (
<div className="simple-counter">
<button className="inc"
data-testid="btn-inc"
onClick={ increase }>+</button>
<span className="value"	
data-testid="value">{ count }</span>
<button className="dec"	
data-testid="btn-dec"
onClick={ decrease }>-</button>
</div>
);
}
const getByTestId =	(node,	id)	=>
node.querySelector(`[data-testid="${id}"]`);
it('초기값은 0이다.',	()	=> {
const value = getByTestId(container,	'value');
expect(value.textContent).toBe('0');
});
it('+	버튼 클릭 시 1	증가한다.',	()	=> {
const btnInc =	getByTestId(container,	'btn-inc');
const value =	getByTestId(container,	'value');
btnInc.click();
expect(value.textContent).toBe('1');
});
const puppeteer =	require('puppeteer');
const {	percySnapshot }	=	require('@percy/puppeteer')
it('Visual	Testing',	async ()	=> {
const browser =	await puppeteer.launch();
const page =	await browser.newPage();
await page.goto('http://localhost:3000/');		
await percySnapshot(page,	'초기 상태');
await browser.close();
});
function Counter({	initValue = 0 })	{
const [count,	setCount]	=	useState(initValue);
//	..
}
ReactDOM.render(
<Counter initValue={8}	/>,
document.getElementById('root')
);
ReactDOM.render(
<Counter initValue={76}	/>,
document.getElementById('root')
);
ReactDOM.render(
<Counter initValue={386}	/>,
document.getElementById('root')
);
it('Visual	Testing',	async ()	=> {
const browser =	await puppeteer.launch();
const page =	await browser.newPage();
await page.goto('http://localhost:3000/single-digit');		
await percySnapshot(page,	'1자리 수');
await page.goto('http://localhost:3000/double-digit');		
await percySnapshot(page,	'2자리 수');
await page.goto('http://localhost:3000/triple-digit');		
await percySnapshot(page,	'3자리 수');
await browser.close();
});
import	React from	'react';
import	Counter from	'../src/Counter';
export default {	title:	'Simple	Counter' };
export const singleDigit =	()	=> <Counter initValue={8}	/>;
export const doubleDigit =	()	=> <Counter initValue={76}	/>;
export const tripleDigit =	()	=> <Counter initValue={386}	/>;
export function Counter({	value,	update })	{
const increase	=	()	=> update('inc');
const decrease	=	()	=> update('dec');
return (
<div className="simple-counter">
..
</div>
);
}
it("+	클릭 시 update('inc')를 호춣한다.",	()	=> {
const updateSpy =	jest.fn();
render(<Counter value={10}	update={updateSpy}	/>);
const btnInc =	getByTestId(container,	'btn-inc');
btnInc.click();
expect(updateSpy).toHaveBeenCalledWith('inc');
});
export const UPDATE_REQUEST =	'UPDATE_REQUEST';
export const UPDATE_SUCCESS =	'UPDATE_SUCCESS';
export function updateRequest(value)	{
return	{	type:	UPDATE_REQUEST,	value }
}
export function updateSuccess(value)	{
return	{	type:	UPDATE_SUCCESS,	value }
}
it('UPDATE_REQUEST	액션 생성',	()	=> {
expect(updateRequest(10)).toEqual({
type:	UPDATE_REQUEST,	
value:	10
})
});
it('UPDATE_SUCCESS	액션 생성',	()	=> {
expect(updateSuccess(11)).toEqual({
type:	UPDATE_SUCCESS,	
value:	11
})
});
export function update(type)	{
return async (dispatch,	getState)	=> {
const currValue =	getState();
dispatch(updateRequest(currValue));
const {	data:	{ value }	}	=	
await axios.put(`/${type}`);
dispatch(updateSuccess(value));
};
}
it("update('inc')	비동기 액션",	async ()	=> {
const middlewares =	[ thunk ];
const mockStore =	configureMockStore(middlewares);
const mockAxios =	new MockAdapter(axios);
mockAxios.onPut('/inc').reply(200,	{	value: 11 });
const store =	mockStore(10);
await store.dispatch(update('inc'));
expect(store.getActions()).toEqual([
{	type: UPDATE_REQUEST,	value: 10 },
{	type: UPDATE_SUCCESS,	value: 11 }
]);
});
export function counter(state, action)	{
switch (action.type)	{
case UPDATE_SUCCESS:
return action.value
default:
return state;
}
}
it('UPDATE_SUCCESS	액션 처리',	()	=> {
const action =	{
type:	UPDATE_SUCCESS,
value:	11
};
expect(counter(10,	action)).toBe(11);
})
리듀서
동기 액션
비동기 액션
연결 컴포넌트
컴포넌트
테스트 단위
it('+	버튼을 클릭하면 1	증가한다',	async ()	=> {
const mockAxios =	new MockAdapter(axios);
mockAxios.onPut('/inc').reply(200,	{	value: 11 });
render(<Provider store={store}><Counter /></Provider>);
const btnInc =	getByTestId(container,	'btn-inc');
const value =	getByTestId(container,	'value');
btnInc.click();
await wait(()	=> {
expect(value.textContent).toBe('11');
});
});
it('+	버튼을 클릭하면 1	증가한다.', ()	=> {
cy.server();
cy.route('PUT', '/inc', {	value: 11 });
cy.visit('/');
cy.findByTestId('btn-inc').click();
cy.findByTestId('value').should('have.text', '11');
});
it('+	버튼을 클릭하면 1	증가한다',	async ()	=> {
const mockAxios =	new MockAdapter(axios);
mockAxios.onPut('/inc').reply(200,	{	value: 11 });
render(
<Provider store={store}>
<Counter />
</Provider>
);
getByTestId(container,	'btn-inc').click();
await wait(()	=> {
expect(getByTestId(container,	'value'))
.toHaveText('11');
});
});
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략

[2019] 실용적인 프런트엔드 테스트 전략

  • 9.
    function Counter() { const [count, setCount]= useState(0); const increase = () => setCount(count + 1); const decrease = () => setCount(count - 1); return ( <div className="simple-counter"> <button className="dec" onClick={decrease}>-</button> <span className="value">{count}</span> <button className="inc" onClick={increase}>+</button> </div> ); }
  • 10.
    it('생성시 버튼과 초기값을렌더링한다.', () => { const div = document.createElement('div'); ReactDOM.render(<Counter />, div); expect(diffableHTML(div.innerHTML)).toBe( diffableHTML(` <div class="simple-counter"> <button class="dec">-</button> <span class="value">0</span> <button class="inc">+</button> </div> `) ); });
  • 11.
    it('생성시 버튼과 초기값을렌더링한다.', () => { const div = document.createElement('div'); ReactDOM.render(<Counter />, div); expect(diffableHTML(div.innerHTML)) .toMatchSnapshot(); }); exports[`생성시 버튼과 초기값을 렌더링한다. 1`] = ` " <div class="simple-counter"> <button class="dec"> - </button> <span class="value"> 0 </span> <button class="inc"> + </button> </div> " `;
  • 16.
    const puppeteer = require('puppeteer'); const{ percySnapshot } = require('@percy/puppeteer') it('Visual Testing', async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://localhost:3000/'); await percySnapshot(page, '초기 상태'); const btnInc = await page.$('button.inc'); await btnInc.click(); await percySnapshot(page, '+ 버튼 클릭 후'); await browser.close(); });
  • 22.
    it('초기값은 0이다.', () => { const value= container.querySelector('.value'); expect(value.innerHTML).toBe('0'); }); it('+ 버튼 클릭 시 1 증가한다.', () => { const btnInc = container.querySelector('button.inc'); const value = container.querySelector('.value'); btnInc.click(); expect(value.innerHTML).toBe('1'); });
  • 23.
  • 24.
    function Counter() { // .. return ( <divclassName="simple-counter"> <button className="inc" data-testid="btn-inc" onClick={ increase }>+</button> <span className="value" data-testid="value">{ count }</span> <button className="dec" data-testid="btn-dec" onClick={ decrease }>-</button> </div> ); } const getByTestId = (node, id) => node.querySelector(`[data-testid="${id}"]`); it('초기값은 0이다.', () => { const value = getByTestId(container, 'value'); expect(value.textContent).toBe('0'); }); it('+ 버튼 클릭 시 1 증가한다.', () => { const btnInc = getByTestId(container, 'btn-inc'); const value = getByTestId(container, 'value'); btnInc.click(); expect(value.textContent).toBe('1'); });
  • 25.
    const puppeteer = require('puppeteer'); const{ percySnapshot } = require('@percy/puppeteer') it('Visual Testing', async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://localhost:3000/'); await percySnapshot(page, '초기 상태'); await browser.close(); });
  • 27.
    function Counter({ initValue =0 }) { const [count, setCount] = useState(initValue); // .. } ReactDOM.render( <Counter initValue={8} />, document.getElementById('root') ); ReactDOM.render( <Counter initValue={76} />, document.getElementById('root') ); ReactDOM.render( <Counter initValue={386} />, document.getElementById('root') );
  • 28.
    it('Visual Testing', async () => { constbrowser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('http://localhost:3000/single-digit'); await percySnapshot(page, '1자리 수'); await page.goto('http://localhost:3000/double-digit'); await percySnapshot(page, '2자리 수'); await page.goto('http://localhost:3000/triple-digit'); await percySnapshot(page, '3자리 수'); await browser.close(); });
  • 31.
    import React from 'react'; import Counter from '../src/Counter'; exportdefault { title: 'Simple Counter' }; export const singleDigit = () => <Counter initValue={8} />; export const doubleDigit = () => <Counter initValue={76} />; export const tripleDigit = () => <Counter initValue={386} />;
  • 43.
    export function Counter({ value, update}) { const increase = () => update('inc'); const decrease = () => update('dec'); return ( <div className="simple-counter"> .. </div> ); } it("+ 클릭 시 update('inc')를 호춣한다.", () => { const updateSpy = jest.fn(); render(<Counter value={10} update={updateSpy} />); const btnInc = getByTestId(container, 'btn-inc'); btnInc.click(); expect(updateSpy).toHaveBeenCalledWith('inc'); });
  • 44.
    export const UPDATE_REQUEST= 'UPDATE_REQUEST'; export const UPDATE_SUCCESS = 'UPDATE_SUCCESS'; export function updateRequest(value) { return { type: UPDATE_REQUEST, value } } export function updateSuccess(value) { return { type: UPDATE_SUCCESS, value } } it('UPDATE_REQUEST 액션 생성', () => { expect(updateRequest(10)).toEqual({ type: UPDATE_REQUEST, value: 10 }) }); it('UPDATE_SUCCESS 액션 생성', () => { expect(updateSuccess(11)).toEqual({ type: UPDATE_SUCCESS, value: 11 }) });
  • 45.
    export function update(type) { returnasync (dispatch, getState) => { const currValue = getState(); dispatch(updateRequest(currValue)); const { data: { value } } = await axios.put(`/${type}`); dispatch(updateSuccess(value)); }; } it("update('inc') 비동기 액션", async () => { const middlewares = [ thunk ]; const mockStore = configureMockStore(middlewares); const mockAxios = new MockAdapter(axios); mockAxios.onPut('/inc').reply(200, { value: 11 }); const store = mockStore(10); await store.dispatch(update('inc')); expect(store.getActions()).toEqual([ { type: UPDATE_REQUEST, value: 10 }, { type: UPDATE_SUCCESS, value: 11 } ]); });
  • 46.
    export function counter(state,action) { switch (action.type) { case UPDATE_SUCCESS: return action.value default: return state; } } it('UPDATE_SUCCESS 액션 처리', () => { const action = { type: UPDATE_SUCCESS, value: 11 }; expect(counter(10, action)).toBe(11); })
  • 52.
    리듀서 동기 액션 비동기 액션 연결컴포넌트 컴포넌트 테스트 단위
  • 53.
    it('+ 버튼을 클릭하면 1 증가한다', async() => { const mockAxios = new MockAdapter(axios); mockAxios.onPut('/inc').reply(200, { value: 11 }); render(<Provider store={store}><Counter /></Provider>); const btnInc = getByTestId(container, 'btn-inc'); const value = getByTestId(container, 'value'); btnInc.click(); await wait(() => { expect(value.textContent).toBe('11'); }); });
  • 58.
    it('+ 버튼을 클릭하면 1 증가한다.',() => { cy.server(); cy.route('PUT', '/inc', { value: 11 }); cy.visit('/'); cy.findByTestId('btn-inc').click(); cy.findByTestId('value').should('have.text', '11'); }); it('+ 버튼을 클릭하면 1 증가한다', async () => { const mockAxios = new MockAdapter(axios); mockAxios.onPut('/inc').reply(200, { value: 11 }); render( <Provider store={store}> <Counter /> </Provider> ); getByTestId(container, 'btn-inc').click(); await wait(() => { expect(getByTestId(container, 'value')) .toHaveText('11'); }); });