ในการพัฒนาเว็บไซต์ด้วย React หลีกเลี่ยงไม่ได้ที่ในส่วนประกอบต่างๆ อาจจำเป็นต้องมีการพึ่งพา Package จากภายนอก เช่น context, props หรืออะไรก็ตามเพื่อให้ทำงานได้อย่างถูกต้อง และส่วนนี่เป็นปัญหาที่เกิดในทุกๆ การทำการสอบ ซึ่งใน cypress ก็มีวิธีการจัดการกับการทำงานในส่วนนี้ไว้ด้วยแล้วเหมือนกัน
เรามาดูตัวอย่างที่เข้าใจง่ายๆ กัน:
Dark & Light Mode
บทความนี้ผมจะยก Code ง่ายๆ ของการใช้งาน Context ใน React มากให้ดูเป็นตัวอย่าง โดยจะสร้างปุ่มง่ายๆ ที่จะมีการเรียกใช้งาน darkMode ผ่าน context ที่ถูกเขียนไว้ที่อื่นในเว็บแอปพลิเคชัน โดยจะเรียกไป hook useDarkMode ซึ่งกำหนดไว้ในโฟลเดอร์อื่นอีกทีนึง
Dark Mode Selector
เริ่มต้นจากการสร้างปุ่ม ที่ทำหน้าที่เปลี่ยน Mode สลับไปมาจาก dark mode เป็น light mode ซึ่งในนี้เป็นตัวอย่างอย่างง่าย เลยให้มันทำหน้าที่แค่เปลี่ยน Mode และข้อความบนปุ่มแทน
import { useDarkMode } from './useDarkMode';
export const DarkModeSelector = () => {
const { darkMode, setDarkMode } = useDarkMode();
return (
<button onClick={() => setDarkMode(!darkMode)}>
{darkMode ? 'Switch to lightmode' : 'Switch to darkmode'}
</button>;
};
export default DarkModeSelector;
DarkModeSelector.tsx
useDarkMode
ต่อมาเราจะสร้างส่วนของการใช้งาน Context โดย จะกำหนด DarkModeProvider ที่รับ Component เข้ามา เพื่อให้สามารถใช้งาน Context ได้
import React, { createContext, useContext } from 'react';
const DarkModeContext = createContext(undefined);
interface DarkModeProviderTypes {
children: React.ReactNode;
value: {
darkMode: boolean;
setDarkMode: Function;
};
}
export const DarkModeProvider = ({ children, value: { darkMode, setDarkMode } }: DarkModeProviderTypes) => (
<DarkModeContext.Provider value={{ darkMode, setDarkMode }}>{children}</DarkModeContext.Provider>
);
export const useDarkMode = () => {
const context = useContext(DarkModeContext);
if (!context) {
throw new Error('useDarkMode must be used within a DarkModeProvider');
}
return context;
};
useDarkMode.tsx
ซึ่งหาการเราลองสร้างไฟล์ทดสอบ แล้วลองเรียกใช้งาน DarkModeSelector โดยตรง เราจะพบว่า Cypress จะแสดง Error ขึ้นมา
import React from 'react'
import DarkModeSelector from './DarkModeSelector'
describe('<DarkModeSelector />', () => {
it('renders', () => {
// see: https://on.cypress.io/mounting-react
cy.mount(<DarkModeSelector />)
})
})
DarkModeSelector.cy.tsx
Error: useDarkMode must be used within a DarkModeProvider
![](https://nutshell.work/content/images/2023/11/Screenshot-2023-11-28-203801.png)
เนื่องจาก DarkModeSelector พยายามใช้ Context ที่ไม่ได้ถูกกำหนดไว้ก่อนที่จะทำการ mounting
วิธีที่ง่ายที่สุดและง่ายที่สุดในการแก้ไข คือ การสร้างตัว wrapper จากนั้นจึง wrap ส่วนของ Component ของเราไว้ในฟังก์ชัน mount
import React, { useState } from 'react';
import { DarkModeProvider } from './useDarkMode';
type DarkModeWrapperTypes = {
children: React.ReactNode;
};
const DarkModeWrapper = ({ children }: DarkModeWrapperTypes) => {
const [darkMode, setDarkMode] = useState(false);
return (
<DarkModeProvider
value={{
darkMode,
setDarkMode,
}}
>
{children}
</DarkModeProvider>
);
};
export default DarkModeWrapper;
DarkModeWrapper.tsx
จากนั้นให้เรียกใช้งาน DarkModeWrapper ในจังหวะ mounting ได้เลย
import DarkModeSelector from './DarkModeSelector';
import DarkModeWrapper from './DarkModeWrapper';
describe('<DarkModeSelector />', () => {
it("renders the 'Switch to darkmode'", () => {
cy.mount(
<DarkModeWrapper>
<DarkModeSelector />
</DarkModeWrapper>
);
cy.contains('Switch to darkmode');
});
})
DarkModeSelector.cy.tsx
เท่านี้เราก็สามารถทดสอบไฟล์ Component ที่มีการเรียกใช้งาน Context ได้แล้ว
การส่งค่าผ่าน Props
จากโค๊ดด้านบน สมมติว่า ถ้าหากเราต้องการส่งค่า initialValue ไปยัง DarkModeWarpper เพื่อกำหนดค่าเริ่มต้นของ mode ก็สามารถทำได้ง่าย
import React, { useState } from 'react';
import { DarkModeProvider } from './useDarkMode';
type DarkModeWrapperTypes = {
children: React.ReactNode;
initialValue?: boolean;
};
const DarkModeWrapper = ({ children, initialValue }: DarkModeWrapperTypes) => {
const [darkMode, setDarkMode] = useState(initialValue || false);
return (
<DarkModeProvider
value={{
darkMode,
setDarkMode,
}}
>
{children}
</DarkModeProvider>
);
};
export default DarkModeWrapper;
DarkModeWrapper.tsx
การใช้งานร่วมกับ Spy
จากสิ่งที่เราทำมาทั้งหมด เป็นเพียงแค่การทดสอบว่า context มีการทำงานที่ถูกต้องหรือไม่ แต่เราไม่สามารถรู้ได้เลยว่า ปุ่ม Button นั้น ได้เรียกใช้งาน setDartMode หรือเปล่า?
ในส่วนนี้เราจะประยุกต์การใช้งานระหว่างตัว warpper context และ การใช้งาน spy ใน cypress เพื่อให้สามารถเข้าไปทดสอบการเรียกใช้งานฟังก์ชั้น setDartMode ใน DarkModeSelector ได้
โดยไปที่ไฟล์ DarkModeWrapper.tsx เราจะทำการเพิ่มฟังก์ชั้น initDarkModeWrapper ขึ้นมา ซึ่งจะเอามันมาครอบฟังก์ชั่น DarkModeWrapper อีกทีนึง
จากนั้นก็จะสร้าง setDarkModeSpy และจัดการ Return ค่าฟังชั่น DarkModeWarpper และ setDarkModeSpy ออกไปใช้งาน ดังในตัวอย่าง
import React, { useState } from 'react';
import { DarkModeProvider } from './useDarkMode';
type DarkModeWrapperTypes = {
children: React.ReactNode;
initialValue?: boolean;
};
const initDarkModeWrapper = () => {
const setDarkModeSpy = cy.spy();
const DarkModeWrapper = ({ children, initialValue }: DarkModeWrapperTypes) => {
const [darkMode, setDarkMode] = useState(initialValue);
return (
<DarkModeProvider
value={{
darkMode,
// setDarkMode,
setDarkMode: (value: boolean) => {
setDarkMode(value);
setDarkModeSpy(value);
},
}}
>
{children}
</DarkModeProvider>
);
};
return [setDarkModeSpy, DarkModeWrapper];
};
export default initDarkModeWrapper;
DarkModeWrapper.tsx
จากนั้นเราจะเข้าไปปรับไฟล์ทดสอบ DarkModeSelector.cy.tsx โดยจะเปลี่ยนมาเรียกใช้งาน DarkModeWarpper จากฟังก์ชั้น initDarkModeWrapper อีกทีนึง
import DarkModeSelector from './DarkModeSelector';
import initDarkModeWrapper from './DarkModeWrapper';
describe('<DarkModeSelector />', () => {
it("renders the 'Switch to darkmode'", () => {
const [, DarkModeWrapper] = initDarkModeWrapper();
cy.mount(
<DarkModeWrapper>
<DarkModeSelector />
</DarkModeWrapper>
);
cy.contains('Switch to darkmode');
});
it("changes context value to 'true' once clicked when initiated with darkmode=false value", () => {
const [setDarkModeSpy, DarkModeWrapper] = initDarkModeWrapper();
cy.mount(
<DarkModeWrapper initialValue={false}>
<DarkModeSelector />
</DarkModeWrapper>
);
cy.get('button').click();
cy.wrap(setDarkModeSpy).should('have.been.called');
});
})
DarkModeSelector.cy.tsx
เท่านี้เราก็สามารถทดสอบฟังก์ชั่น setDarkMode ได้แล้วว่า มีการเรียกใช้งานได้อย่างถูกต้องหรือไม่