کتابخانه تست مؤلفه خود را بسازید: راهنمای توسعه دهنده

من به عنوان یک نویسنده پرفروش ، شما را دعوت می کنم تا کتابهای من را در آمازون کشف کنید. فراموش نکنید که مرا در متوسط دنبال کنید و پشتیبانی خود را نشان دهید. ممنون حمایت شما به معنای جهان است!
JavaScript در توسعه وب متحول شده است و معماری های مبتنی بر مؤلفه به استاندارد تبدیل شده اند. آزمایش این مؤلفه ها به طور کامل به ابزارهای تخصصی نیاز دارد. من سالها در ساخت کتابخانه های آزمایش صرف کرده ام و می خواهم تکنیک هایی را برای ایجاد راه حل تست اجزای خود به اشتراک بگذارم.
ارائه DOM
پایه و اساس هر کتابخانه تست مؤلفه ارائه DOM است. هنگامی که من برای اولین بار با این چالش مقابله کردم ، متوجه شدم که نیاز به هر دو روش آزمایش جدا شده و یکپارچه.
ایجاد یک رندر DOM سبک با ایجاد یک محیط منزوی شروع می شود. اجرای DOM مجازی عملکرد بهتری نسبت به دستکاری DOM واقعی ارائه می دهد.
function createRenderer() {
const virtualDOM = document.createElement('div');
return {
render(component) {
ReactDOM.render(component, virtualDOM);
return virtualDOM;
},
shallowRender(component) {
// Prevent children from rendering deeply
const shallowContext = createShallowContext();
ReactDOM.render(
<ShallowRenderer context={shallowContext}>
{component}
</ShallowRenderer>,
virtualDOM
);
return virtualDOM;
},
cleanup() {
ReactDOM.unmountComponentAtNode(virtualDOM);
}
};
}
// Usage
const renderer = createRenderer();
const element = renderer.render(<Button>Click Me</Button>);
expect(element.textContent).toBe('Click Me');
renderer.cleanup();
برای ارائه کم عمق ، فهمیدم که رهگیری روند آشتی React بهترین کار را انجام می دهد. این امر به ما امکان می دهد مؤلفه های کودک را با صاحبان مکان جایگزین کنیم و آزمایشات خود را بر روی رفتار خاص این مؤلفه بدون آزمایش فرزندان خود متمرکز کنیم.
function ShallowRenderer({ children, context }) {
return React.cloneElement(children, {}, null);
}
function createShallowContext() {
const componentStubs = new Map();
return {
registerComponent(Component, stub) {
componentStubs.set(Component, stub);
},
getStub(Component) {
return componentStubs.get(Component) || null;
}
};
}
شبیه سازی رویداد
شبیه سازی دقیق رویداد به دلیل تفاوت های مرورگر چالش برانگیز بود. من یاد گرفتم که اشیاء رویداد عادی ایجاد کنم که در محیط ها کار می کنند.
function createEventSimulator() {
return {
click(element, options = {}) {
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
...options
});
element.dispatchEvent(clickEvent);
},
change(element, value) {
// Set the value
element.value = value;
// Trigger change event
const event = new Event('change', {
bubbles: true,
cancelable: true
});
element.dispatchEvent(event);
},
keyPress(element, key, options = {}) {
const keyEvent = new KeyboardEvent('keypress', {
key,
bubbles: true,
cancelable: true,
...options
});
element.dispatchEvent(keyEvent);
},
drag(element, { from, to }) {
// Simulate dragstart
const dragStartEvent = new MouseEvent('dragstart', {
bubbles: true,
clientX: from.x,
clientY: from.y
});
element.dispatchEvent(dragStartEvent);
// Simulate dragover on target
const dragOverEvent = new MouseEvent('dragover', {
bubbles: true,
clientX: to.x,
clientY: to.y
});
element.dispatchEvent(dragOverEvent);
// Simulate drop on target
const dropEvent = new MouseEvent('drop', {
bubbles: true,
clientX: to.x,
clientY: to.y
});
element.dispatchEvent(dropEvent);
}
};
}
رویدادهای لمسی برای شبیه سازی صحیح تعامل چند لمسی نیاز به هندلینگ ویژه دارند:
function simulateTouch(element, touchType, touchPoints) {
const touchEvent = new TouchEvent(touchType, {
bubbles: true,
cancelable: true,
touches: createTouchList(touchPoints),
targetTouches: createTouchList(touchPoints),
changedTouches: createTouchList(touchPoints)
});
element.dispatchEvent(touchEvent);
}
function createTouchList(points) {
return points.map((point, id) => new Touch({
identifier: id,
target: element,
clientX: point.x,
clientY: point.y
}));
}
// Usage example
simulateTouch(element, 'touchstart', [
{ x: 100, y: 100 },
{ x: 200, y: 150 }
]);
تأیید دولت
آزمایش مؤثر مؤثر نیاز به اعتبار سنجی خروجی DOM و وضعیت داخلی دارد. من ادعاهای تخصصی برای انواع مؤلفه های مختلف ایجاد کرده ام.
const componentAssertions = {
hasClass(element, className) {
return element.classList.contains(className);
},
hasAttribute(element, name, value) {
if (value === undefined) {
return element.hasAttribute(name);
}
return element.getAttribute(name) === value;
},
hasStyle(element, property, value) {
const style = window.getComputedStyle(element);
return style[property] === value;
},
isVisible(element) {
const style = window.getComputedStyle(element);
return style.display !== 'none' &&
style.visibility !== 'hidden' &&
style.opacity !== '0';
},
containsText(element, text) {
return element.textContent.includes(text);
},
matchesAccessibility(element, criteria) {
// Check ARIA attributes
if (criteria.role && element.getAttribute('role') !== criteria.role) {
return false;
}
// Check for required ARIA attributes based on role
if (criteria.required) {
for (const attr of criteria.required) {
if (!element.hasAttribute(`aria-${attr}`)) {
return false;
}
}
}
return true;
}
};
برای اجزای فرم ، من ادعاهای تخصصی ایجاد کردم:
const formAssertions = {
hasValue(input, expected) {
return input.value === expected;
},
isChecked(checkbox) {
return checkbox.checked === true;
},
isDisabled(element) {
return element.disabled === true;
},
hasError(formElement, errorMessage) {
// Find associated error element
const id = formElement.id;
const errorElement = document.querySelector(`[aria-describedby="${id}"]`);
return errorElement && errorElement.textContent.includes(errorMessage);
},
isValid(formElement) {
return formElement.validity.valid;
}
};
آزمایش ناهمزمان
رسیدگی به رفتار مؤلفه Async همیشه یک چالش بوده است. من برنامه هایی ایجاد کرده ام که زمان بندی و انتقال دولت را مدیریت می کند.
async function waitFor(predicate, options = {}) {
const { timeout = 1000, interval = 50 } = options;
const startTime = Date.now();
return new Promise((resolve, reject) => {
const check = () => {
try {
const result = predicate();
if (result) {
resolve(result);
return;
}
} catch (error) {
// Predicate threw an error, keep waiting
}
const elapsed = Date.now() - startTime;
if (elapsed >= timeout) {
reject(new Error(`Timed out after ${timeout}ms waiting for predicate to be true`));
return;
}
setTimeout(check, interval);
};
check();
});
}
async function waitForElement(selector, container = document) {
return waitFor(() => container.querySelector(selector));
}
async function waitForRender(component) {
// Force a microtask to allow React to render
await Promise.resolve();
// For React 18+ with concurrent features
if (typeof window.ReactDOM?.flushSync === 'function') {
ReactDOM.flushSync();
}
return component;
}
async function waitForAnimation(element) {
if (!element.getAnimations) {
// Fallback for browsers without Animation API
return waitFor(() =>
!element.classList.contains('animating') &&
!element.style.animation
);
}
return waitFor(() => element.getAnimations().every(a => a.playState === 'finished'));
}
به روزرسانی های حالت React نیاز به تکنیک های تخصصی دارد:
function act(callback) {
// Use React's test utilities if available
if (typeof React.act === 'function') {
return React.act(callback);
}
// Fallback implementation
callback();
// Flush microtasks
return Promise.resolve();
}
async function waitForUpdate(component, propName, expectedValue) {
return waitFor(() => {
const currentProps = component.props;
return currentProps[propName] === expectedValue;
});
}
// Example usage
await act(async () => {
fireEvent.click(button);
await waitForUpdate(component, 'isLoading', false);
});
تولید عکس فوری
آزمایش عکس فوری به تشخیص تغییرات ناخواسته کمک می کند. من ابزارهایی ایجاد کرده ام که نمایش های سریال قابل استفاده از قطعات را ایجاد می کنند.
function createSnapshotter() {
return {
captureDOM(element) {
return {
tagName: element.tagName.toLowerCase(),
attributes: getAttributes(element),
children: Array.from(element.childNodes).map(node => {
if (node.nodeType === Node.TEXT_NODE) {
return { type: 'text', content: node.textContent };
}
return this.captureDOM(node);
}),
textContent: element.textContent
};
},
captureComponent(component) {
const renderer = createRenderer();
const element = renderer.render(component);
const snapshot = this.captureDOM(element);
renderer.cleanup();
return snapshot;
},
compare(snapshot1, snapshot2) {
const differences = [];
compareNodes(snapshot1, snapshot2, '', differences);
return {
hasDifferences: differences.length > 0,
differences
};
}
};
}
function getAttributes(element) {
const attributes = {};
Array.from(element.attributes).forEach(attr => {
attributes[attr.name] = attr.value;
});
return attributes;
}
function compareNodes(node1, node2, path, differences) {
if (!node1 || !node2) {
differences.push({
path,
message: !node1 ? 'Node added' : 'Node removed'
});
return;
}
if (node1.type !== node2.type) {
differences.push({
path,
message: `Node type changed from ${node1.type} to ${node2.type}`
});
return;
}
if (node1.type === 'text' && node1.content !== node2.content) {
differences.push({
path,
message: `Text changed from "${node1.content}" to "${node2.content}"`
});
return;
}
if (node1.tagName !== node2.tagName) {
differences.push({
path,
message: `Tag changed from ${node1.tagName} to ${node2.tagName}`
});
}
// Compare attributes
const allAttributes = new Set([
...Object.keys(node1.attributes || {}),
...Object.keys(node2.attributes || {})
]);
for (const attr of allAttributes) {
const val1 = node1.attributes?.[attr];
const val2 = node2.attributes?.[attr];
if (val1 !== val2) {
differences.push({
path: `${path}.attributes[${attr}]`,
message: `Attribute "${attr}" changed from "${val1}" to "${val2}"`
});
}
}
// Compare children
const children1 = node1.children || [];
const children2 = node2.children || [];
const maxLength = Math.max(children1.length, children2.length);
for (let i = 0; i < maxLength; i++) {
compareNodes(
children1[i],
children2[i],
`${path}.children[${i}]`,
differences
);
}
}
زمینه مؤثر
مؤلفه ها به ندرت در انزوا وجود دارند. آنها برای مضامین ، ایالت ، مسیریابی و موارد دیگر به ارائه دهندگان زمینه بستگی دارند. من رویکردهایی را برای مسخره کردن این زمینه ها ایجاد کرده ام.
function createContextMock(Context, defaultValue) {
return {
withValue(value, children) {
return (
<Context.Provider value={value}>
{children}
</Context.Provider>
);
},
peek() {
// Get current context value
let currentValue = defaultValue;
const Probe = () => {
currentValue = React.useContext(Context);
return null;
};
const renderer = createRenderer();
renderer.render(<Probe />);
renderer.cleanup();
return currentValue;
}
};
}
// Mock theme context
const ThemeContext = React.createContext({ mode: 'light' });
const themeMock = createContextMock(ThemeContext, { mode: 'light' });
// Test with dark theme
const { container } = render(
themeMock.withValue({ mode: 'dark' }, <Button>Submit</Button>)
);
expect(container.querySelector('.button')).toHaveClass('dark-theme');
برای زمینه پیچیده تر مانند فروشگاه ها یا روترها:
function createStoreMock(initialState = {}) {
let state = { ...initialState };
const listeners = new Set();
return {
getState() {
return { ...state };
},
setState(newState) {
state = { ...state, ...newState };
listeners.forEach(listener => listener(state));
},
subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
withStore(component) {
return (
<StoreContext.Provider value={this}>
{component}
</StoreContext.Provider>
);
}
};
}
function createRouterMock(initialRoute = '/') {
let currentRoute = initialRoute;
const listeners = new Set();
return {
getCurrentRoute() {
return currentRoute;
},
navigate(route) {
currentRoute = route;
listeners.forEach(listener => listener(currentRoute));
},
subscribe(listener) {
listeners.add(listener);
return () => listeners.delete(listener);
},
withRouter(component) {
return (
<RouterContext.Provider
value={{
route: currentRoute,
navigate: this.navigate.bind(this)
}}
>
{component}
</RouterContext.Provider>
);
}
};
}
با هم قرار دادن همه اینها ، من یک ابزار آزمایش جامع ایجاد کرده ام:
function createTestBed() {
const container = document.createElement('div');
document.body.appendChild(container);
const events = createEventSimulator();
const assertions = { ...componentAssertions, ...formAssertions };
const snapshotter = createSnapshotter();
return {
render(component) {
ReactDOM.render(component, container);
return {
container,
// Selection methods
getByTestId: (id) => container.querySelector(`[data-testid="${id}"]`),
getByText: (text) => [...container.querySelectorAll('*')]
.find(el => el.textContent.trim() === text),
queryBySelector: (selector) => container.querySelector(selector),
// Event methods
fireEvent: events,
// Assertion methods
assert: assertions,
// Async methods
waitFor,
waitForElement: (selector) => waitForElement(selector, container),
// Snapshot methods
snapshot: () => snapshotter.captureDOM(container),
// Cleanup
unmount: () => {
ReactDOM.unmountComponentAtNode(container);
}
};
},
cleanup() {
document.body.removeChild(container);
},
// Context mocking
createContextMock,
createStoreMock,
createRouterMock
};
}
// Example usage
const testBed = createTestBed();
const { getByTestId, fireEvent, assert, waitFor, unmount } = testBed.render(
<Counter initialCount={0} />
);
const countDisplay = getByTestId('count-display');
const incrementButton = getByTestId('increment-button');
expect(assert.containsText(countDisplay, '0')).toBe(true);
fireEvent.click(incrementButton);
waitFor(() => assert.containsText(countDisplay, '1'))
.then(() => {
console.log('Test passed!');
unmount();
testBed.cleanup();
});
ساختن یک کتابخانه آزمایش مؤلفه درک من از رفتار جاوا اسکریپت و مرورگر را عمیق تر کرده است. تکنیک هایی که من به اشتراک گذاشته ام ، آزمایش جامع مؤلفه را فراهم می کند ، از ارائه DOM ساده گرفته تا رفتار پیچیده ناهمزمان و تمسخر متن.
این الگوهای امروز پایه و اساس بسیاری از کتابخانه های تست محبوب امروز را تشکیل می دهند. با درک نحوه کار آنها در زیر کاپوت ، شما برای سفارشی کردن راه حل های آزمایش برای نیازهای خاص خود ، انعطاف پذیری کسب می کنید.
به یاد داشته باشید که آزمایش مربوط به اعتماد به نفس است ، نه پوشش. بهترین کتابخانه تست کتابی است که به شما در نوشتن تست های معنی دار با حداقل اصطکاک کمک می کند. تکنیک هایی که من به اشتراک گذاشته ام به شما امکان می دهد چنین کتابخانه ای را متناسب با فلسفه تست خاص و معماری مؤلفه خود بسازید.
101 کتاب
101 کتاب یک شرکت انتشارات AI محور است که توسط نویسنده تأسیس شده است آراو جوشیبشر با استفاده از فناوری پیشرفته هوش مصنوعی ، ما هزینه های انتشار خود را فوق العاده کم نگه می داریم – برخی از کتاب ها به اندازه کم قیمت هستند 4 دلار– ایجاد دانش با کیفیت در دسترس همه.
کتاب ما را بررسی کنید کد تمیز Golang در آمازون موجود است.
برای به روزرسانی ها و اخبار هیجان انگیز با ما در ارتباط باشید. هنگام خرید کتاب ، جستجو کنید آراو جوشی برای یافتن بیشتر عناوین ما. برای لذت بردن از لینک ارائه شده استفاده کنید تخفیف های خاص!
خلاقیت های ما
حتما خلاقیت های ما را بررسی کنید:
سرمایه گذار مرکزی | سرمایه گذار اسپانیایی مرکزی | سرمایه گذار آلمانی مرکزی | زندگی هوشمند | دوره ها و پژواک | اسرار گیج کننده | هندوتوا | نخبه | مدارس JS
ما در متوسط هستیم
بینش های فنی Koala | Epochs & Echoes World | سرمایه گذار رسانه مرکزی | رمز و رازهای گیج کننده متوسط | علوم و دوره های متوسط | هندوتوا مدرن