Luyện Phỏng Vấn IT — 2000+ Câu Hỏi Phỏng Vấn IT Có Đáp Án 2026
Testing
Jest là JavaScript testing framework từ Meta, phổ biến nhất trong ecosystem.
- Zero-config với Create React App và Next.js; TypeScript support qua ts-jest hoặc babel-jest.
- Test isolation: mỗi test file chạy trong separate Node.js environment (jsdom hoặc node), global state không leak giữa files.
- Parallel execution: Jest chạy test files song song qua worker pool, isolate failures tốt.
- Snapshot testing: serialize output và so sánh với .snap files — detect unexpected UI changes.
- Mocking system: jest.fn(), jest.mock(), jest.spyOn() — mock modules, timers (jest.useFakeTimers), và globals.
- Code coverage: --coverage flag generate Istanbul reports (statements, branches, functions, lines).
- Watch mode: --watchAll hoặc --watch chỉ re-run tests affected by changes.
- Matchers: toBe (===), toEqual (deep), toMatchObject (partial match), toThrow, toHaveBeenCalledWith, resolves/rejects cho async.
- Setup files: jest.config.js với setupFilesAfterEach để configure Testing Library, mock global APIs.
- Tích hợp với React Testing Library: render, screen, fireEvent, userEvent — test behavior không implementation details.
describe nhóm related tests thành suite (có thể nest nhiều cấp). it/test định nghĩa test case — tên test nên đọc như documentation: it('should return 404 when user not found'). expect tạo assertion với matchers: toBe (strict ===, dùng cho primitives), toEqual (deep equality, dùng cho objects/arrays), toMatchObject (partial object match — chỉ check specified keys), toContain (array/string contains), toHaveLength, toBeNull/toBeUndefined/toBeTruthy/toBeFalsy, toThrow (function throw error), toHaveBeenCalledWith (mock assertion).
Async matchers: await expect(promise).resolves.toBe(value) hoặc await expect(promise).rejects.toThrow(). beforeEach/afterEach chạy trước/sau mỗi test trong describe block — dùng cho setup/teardown (tạo test data, cleanup database, reset mocks). beforeAll/afterAll chạy một lần cho cả describe block — dùng cho expensive setup (start test server, database connection). test.only/describe.only: chỉ chạy selected tests — hữu ích khi debug nhưng đừng commit. test.skip: bỏ qua test tạm thời với reason. test.each: parameterized tests với multiple input/output combinations.
jest.fn() tạo mock function tracking calls, arguments, return values — mockReturnValue(val) cho sync, mockResolvedValue(val) cho Promise, mockRejectedValue(err) cho rejected Promise, mockImplementation(fn) cho custom logic. jest.mock('module-path') auto-mock toàn bộ module (tất cả exports thành jest.fn()); với factory function jest.mock('./api', () => ({ fetchUser: jest.fn() })) cho control toàn diện; hoisting: jest.mock() được hoisted lên đầu file tự động. jest.spyOn(obj, 'method') wrap existing method — vẫn gọi real implementation mặc định, dùng .mockImplementation() để override; quan trọng: phải restore sau test bằng jest.restoreAllMocks() hoặc mockRestore().
- Module mock patterns: mock fetch API toàn cục với jest.spyOn(global, 'fetch'); mock environment variables với process.env.MY_VAR = 'test'.
- Timer mocks: jest.useFakeTimers() replace setTimeout/setInterval/Date; jest.runAllTimers() trigger tất cả pending timers; jest.advanceTimersByTime(ms).
- Partial mocks: jest.requireActual('module') để mock một phần module và giữ lại real implementation cho phần còn lại. clearMocks vs resetMocks vs restoreMocks: clear (reset calls/instances), reset (clear + remove implementations), restore (reset + restore spies to originals).
Vitest là test framework xây dựng trên Vite với Jest-compatible API — migrate từ Jest thường chỉ cần đổi import và config.
- Lợi ích: reuse Vite config và transforms (cùng aliases, plugins, TypeScript setup) — không cần config riêng cho tests; nhanh hơn Jest nhờ esbuild transpilation và native ESM.
- Watch mode thông minh: chỉ re-run tests liên quan đến changed files, smart dependency tracking qua Vite HMR graph.
- In-source testing: viết tests trực tiếp trong source file trong if (import.meta.vitest) block — tests bị tree-shaken khỏi production build, hữu ích cho utility functions.
- Browser mode: chạy tests trong real browser (Chromium/Firefox) thay vì jsdom — accurate DOM behavior, dùng cho component tests.
- Concurrent tests: test.concurrent() chạy tests trong describe block song song, tăng speed.
- Snapshot: tương thích với Jest snapshots, có inline snapshots.
- Coverage: V8 provider (nhanh hơn) hoặc Istanbul. @vitest/ui: browser-based test runner UI để visualize và debug tests.
- Khác biệt với Jest: Vitest dùng ES modules natively (không transform), config trong vitest.config.ts thay vì jest.config.js, một số globals như __dirname cần config thêm.
Cypress chạy trong cùng event loop với app (không qua WebDriver) — time-travel debugging, auto-retry, và cy.intercept() là điểm mạnh; nhược điểm: chỉ Chromium+Firefox, không support multiple tabs, không có async/await native.
- Cypress là E2E testing framework chạy trong browser (không qua WebDriver protocol như Selenium).
- Architecture khác biệt: Cypress chạy trong cùng event loop với app — có thể access application state, intercept network requests, control timers trực tiếp.
- Time-travel debugging: mỗi command được snapshot, hover qua command list để xem DOM state tại từng bước — exceptional debugging experience.
- Automatic waiting: Cypress tự retry assertions cho đến khi pass hoặc timeout — không cần manual sleep/waitForElement như Selenium. cy.intercept() cho network stubbing: mock API responses, simulate network errors, wait for specific requests to complete.
- Component testing: Cypress Component Testing mount individual components trong isolation — alternative cho Jest + Testing Library với real browser. cy.session() để cache và reuse authentication state giữa tests — giảm login overhead.
- Cypress Cloud: parallel test execution, flaky test detection, video recording.
- Nhược điểm: chỉ support Chromium-based browsers và Firefox (không có Safari/WebKit); không support multiple tabs trong cùng test; test code phải bất đồng bộ theo Cypress command queue — không dùng async/await thông thường.
- Thích hợp: rich interactive app testing, khi cần time-travel debugging, single-browser test suites.
Playwright là lựa chọn hiện đại cho E2E testing — cross-browser thực sự (Chromium/Firefox/WebKit), async/await native, parallel execution built-in, Trace Viewer cho debug CI failures.
- Playwright là E2E testing framework từ Microsoft, hỗ trợ Chromium, Firefox, WebKit (Safari) — cross-browser coverage thực sự.
- Parallel test execution built-in: test files chạy song song theo default, có thể config workers; Cypress Cloud tính thêm phí cho parallelization.
- Multiple tabs/contexts/pages: test multi-tab flows như OAuth popup, new window; browser contexts là isolated sessions (khác profiles).
- Page Object Model tự nhiên: Playwright khuyến khích tổ chức code theo POM pattern với class-based pages. await/async API: test code dùng standard async/await — dễ đọc và debug hơn Cypress command queue.
- Network interception: page.route('/api/', route => route.fulfill({ json: mockData })) — mạnh và flexible; page.waitForResponse() để wait cho specific API call.
- Locators: page.getByRole('button', { name: 'Submit' }), getByText(), getByTestId() — semantic locators như Testing Library, auto-retry built-in.
- Visual regression testing: expect(page).toHaveScreenshot() so sánh screenshots.
- Tracing: tạo trace.zip với screenshots, network logs, DOM snapshots — mở trong Playwright Trace Viewer để debug CI failures.
- Playwright Inspector: step-through test execution, pick locators từ browser.
- So với Cypress: Playwright có better multi-browser support và parallel execution; Cypress có better DX và debugging cho single-browser.
TDD viết test trước code — quy trình Red-Green-Refactor: Red (viết test cho behavior mong muốn, test fail vì code chưa có), Green (viết minimum code đủ để test pass — không over-engineer), Refactor (cải thiện code structure, remove duplication trong khi tests vẫn pass).
- Lợi ích thực tế: buộc suy nghĩ về API/interface trước implementation — kết quả là better-designed, more testable code; coverage cao tự nhiên vì code chỉ được viết khi có test; dễ refactor vì test suite là safety net.
Ví dụ: test 'calculateTax(100, 0.1) should return 10' → implement calculateTax → refactor nếu cần.
- Micro-TDD cycles: mỗi cycle 2-10 phút, test một behavior nhỏ.
- TDD không phải 'test first' một cách cứng nhắc — nhiều developers làm 'test alongside' (viết test song song với code).
- Khi TDD khó: legacy code không testable (cần refactor trước), UI-heavy code (E2E tests khó TDD), exploratory code khi chưa biết design.
- Detroit vs London schools: Detroit (classic TDD, bottom-up, focus on state verification, minimal mocking); London (mockist TDD, top-down với mocks cho collaborators, verify interactions).
- Practical advice: TDD đặc biệt valuable cho business logic, algorithms, utilities; ít valuable cho simple CRUD và UI layout.
BDD là extension của TDD, focus vào behavior từ user/business perspective thay vì technical implementation.
- Given-When-Then syntax: Given (context/preconditions — 'Given a user with $100 in account'), When (action — 'When they transfer $50'), Then (expected outcome — 'Then the balance should be $50 and recipient has $50').
- Gherkin language: human-readable scenarios viết trong .feature files — Given/When/Then/And/But; được Cucumber parse và map đến step definitions trong code.
- Lợi ích: common language giữa developers, QA, và business stakeholders; scenarios serve as living documentation; non-technical people có thể review và write scenarios.
- Trong Jest/Vitest: BDD style với describe/it — it('should deduct amount from sender balance when transfer succeeds') đọc như specification.
- Cucumber.js: feature files → step definitions (JS functions) → actual test code; overkill cho most projects trừ khi business stakeholders thực sự write scenarios.
- Practical BDD: dùng Given-When-Then naming convention trong unit tests mà không cần Cucumber — captures intent, readable as docs.
Pitfall: BDD không phải chỉ về syntax — phải thực sự involve business trong defining behaviors, không chỉ wrap existing tests với Given/When/Then.
Test pyramid của Mike Cohn: base là nhiều unit tests (nhanh <1ms, rẻ, isolated, dễ maintain), giữa là integration tests (test interaction giữa components/services với real dependencies), đỉnh là ít E2E tests (chậm, đắt, brittle — dễ fail vì network, timing, UI changes).
- Lý do balance 70/20/10: unit tests cho fast feedback và precise error location; integration tests catch boundary bugs unit tests miss; E2E tests verify user journeys nhưng cost cao.
- Ice cream cone anti-pattern (inverted pyramid): nhiều E2E, ít unit — CI chạy chậm, flaky tests, khó debug khi fail.
- Testing Trophy (Kent C.
- Dodds): variation với nhiều integration tests hơn pyramid — 'integration tests give the most confidence per dollar of test cost'.
- Service layer testing: test business logic through service/use-case layer với real database (integration) thay vì mock everything — catches more real bugs.
- Practical: không nên có ratio cứng nhắc, phụ thuộc vào project.
- Smoke tests: minimal E2E tests chạy sau mỗi deployment để verify critical paths hoạt động.
- Contract tests thay E2E trong microservices.
- Key insight: fast tests chạy thường xuyên → fast feedback → catch bugs sớm; slow E2E tests chạy ít hơn → feedback chậm.
Snapshot testing serialize output (component render, JSON object, string) và save vào .snap file — subsequent runs compare với saved snapshot, fail nếu thay đổi.
- Khi thay đổi là intentional: jest --updateSnapshot (hoặc -u) update tất cả snapshots; jest --updateSnapshot --testNamePattern để update specific.
- Inline snapshots: toMatchInlineSnapshot() lưu snapshot trong test file thay vì separate .snap file — dễ review hơn trong PRs nhưng clutters test files.
- Khi nên dùng: stable UI components ít thay đổi (Button, Card, Input), serializable data structures, API response shapes.
- Khi không nên dùng: frequently changing components (mỗi PR update snapshot không meaningful), dynamic content (timestamps, random IDs — phải mock), complex business logic (unit tests với explicit assertions tốt hơn).
Pitfall phổ biến: commit outdated snapshots khi developers chạy update mà không review changes — snapshot tests trở thành rubber stamp.
- Tốt nhất: kết hợp snapshot tests với explicit assertions cho critical properties.
- Enzyme vs React Testing Library: Enzyme snapshots test implementation details (component structure); RTL khuyến khích test user-visible output.
- Custom serializers: jest.addSnapshotSerializer() để format snapshot output đẹp hơn cho custom objects.
Code coverage đo phần trăm code được chạy bởi tests — generated bởi Istanbul (Jest), V8 (Vitest V8 provider).
- Metrics: Statement coverage (% statements executed — mỗi expression/assignment là statement); Branch coverage (% branches covered — mỗi if/else/ternary/&&/|| tạo 2 branches, phải test cả true và false case); Function coverage (% functions called ít nhất một lần); Line coverage (% lines executed — thường cao nhất, ít meaningful nhất).
- Branch coverage là metric quan trọng nhất — 100% statement nhưng 70% branch nghĩa là nhiều edge cases chưa được test. 100% coverage không đảm bảo bug-free: test có thể chạy code nhưng không assert correctly; test với wrong inputs không catch bugs với right inputs.
- Low coverage là red flag: dưới 60-70% thường là warning sign.
- Coverage thresholds trong jest.config.js: coverageThreshold: { global: { branches: 80, functions: 80 } } — fail CI nếu không đạt.
- Practical targets: 80-90% cho business logic; 70%+ overall; không chase 100% cho UI components và configuration files.
- Excluded from coverage: vendor code, generated files, test utilities — dùng coveragePathIgnorePatterns.
- Coverage reports: text (CI), html (visual inspection), lcov (SonarQube, Codecov).
- Mutation testing (Stryker): modify code và check nếu tests fail — đo test quality, không chỉ coverage quantity.
Unit testing: test một function/class isolated, mock tất cả external dependencies — nhanh (<1ms), stable, dễ debug khi fail (chính xác biết chỗ nào sai), không cần external services.
- Tốt cho: algorithms, business logic, pure functions, utilities.
- Integration testing: test interaction giữa multiple components/services với real hoặc minimal-mock dependencies — ví dụ test API handler với real database (test container), test service với real HTTP client.
- Chậm hơn (100ms-1s), phát hiện bugs ở boundary (schema mismatch, query lỗi, API contract violation) mà unit tests không thể catch.
- Supertest pattern (Node.js): const app = express(); const response = await request(app).get('/api/users').expect(200) — test Express routes với real middleware stack, mock chỉ external services.
- Test containers: spin up real PostgreSQL/Redis trong Docker cho tests — deterministic, isolated, cleanup sau tests.
- Contract tests thay thế integration tests trong microservices khi services owned bởi different teams.
- Testing Library philosophy: 'The more your tests resemble the way your software is used, the more confidence they can give you' — integration tests thường cho more confidence per test than unit tests.
- Mock database vs real database: mock nhanh hơn nhưng có thể miss SQL syntax errors, constraint violations, query performance issues — real DB catches more real-world bugs.
Bộ công cụ phổ biến nhất để test React components là Jest kết hợp React Testing Library (RTL), trong đó RTL theo triết lý test hành vi người dùng thay vì chi tiết implementation bên trong.
Cách viết test cơ bản là dùng render() để mount component, screen.getByRole() hoặc screen.getByText() để tìm element, rồi userEvent.click() để mô phỏng tương tác. Để mock API calls, nên dùng MSW (Mock Service Worker) vì nó chặn requests ở tầng network, giúp test giống thực tế hơn so với mock trực tiếp fetch/axios.
Nguyên tắc quan trọng là viết test theo luồng tương tác của user thực — ví dụ click nút submit rồi kiểm tra hiển thị thông báo thành công, thay vì kiểm tra state nội bộ có thay đổi hay không.
Test pyramid gồm ba tầng:
- Unit test ở đáy — nhiều nhất, chạy nhanh, test từng function hoặc component riêng lẻ bằng Jest hoặc Vitest.
- Integration test ở giữa — test sự tương tác giữa các component hoặc module bằng Testing Library.
- E2E test ở đỉnh — ít nhất, chạy chậm nhất, test toàn bộ luồng người dùng bằng Cypress hoặc Playwright
Chiến lược hợp lý: unit test phủ rộng logic nghiệp vụ, integration test cho các luồng quan trọng, E2E chỉ cho critical path như đăng nhập, thanh toán.
Mục tiêu là có feedback nhanh từ unit test và độ tin cậy cao từ E2E.
AAA là cấu trúc chuẩn cho mỗi unit test, giúp test dễ đọc và maintain. Arrange (chuẩn bị): khởi tạo data, mocks, objects cần thiết — đây là phần dài nhất. Act (thực hiện): gọi function/method đang test — chỉ một dòng, test một behavior duy nhất. Assert (kiểm tra kết quả): verify output hoặc side effect.
Ví dụ: // Arrange const cart = new Cart(); cart.addItem({ id: 1, price: 100 }); // Act const total = cart.getTotal(); // Assert expect(total).toBe(100).
- Lợi ích: phân chia rõ ràng giúp debug nhanh (Arrange sai → test data lỗi, Act sai → function không tồn tại, Assert sai → logic bug).
- Nếu Act phần có nhiều dòng, đó là dấu hiệu function đang test quá nhiều trách nhiệm.
Pitfall: expect trong Arrange (assert setup đúng) làm mờ ranh giới — chỉ assert kết quả cuối trong Assert section.
- AAA tương đương Given-When-Then của BDD — cùng concept, khác naming.
MSW intercept network requests ở service worker level (browser) hoặc Node.js (msw/node), cho phép mock API responses mà không thay đổi code của app.
- Khác với mock fetch trực tiếp (
jest.spyOn(global, 'fetch')): MSW intercept ở network layer — app code không biết gì, không cần wrap hay inject mock vào component. - Setup:
const server = setupServer(rest.get('/api/users', (req, res, ctx) => res(ctx.json([{ id: 1, name: 'Alice' }]))))→server.listen()trong beforeAll,server.resetHandlers()trong afterEach,server.close()trong afterAll. - Lợi ích: test component behavior với realistic API responses (không cần mock React Query, SWR, hay fetch); test error states bằng
server.use(rest.get('/api/users', (req, res, ctx) => res(ctx.status(500))))— override handler cho một test cụ thể; reuse handlers giữa tests và browser development (intercept trong dev cũng được). - MSW v2 dùng
http.get()/http.post()thay vìrest.get().
Pitfall: phải setup msw/node cho Jest (không có service worker trong Node.js); cần polyfill fetch trong Node.js environment trước khi Jest 28.
Flaky tests là tests đôi khi pass, đôi khi fail với cùng code — cực kỳ độc hại cho CI/CD vì giảm trust vào test suite và làm team bỏ qua failing tests.
Nguyên nhân phổ biến:
- Timing/async issues — test không chờ đúng async operation, dùng arbitrary
setTimeoutthay vìwaitFor/explicit await; - Test order dependency — test A phụ thuộc vào side effects của test B;
- Shared mutable state — global state không reset giữa tests;
- External dependencies — gọi real APIs, database không isolated;
- Date/time — dùng
new Date()thay vì mock; - Random data — không seed random generator
Cách xử lý: waitFor(() => expect(element).toBeInTheDocument()) trong Testing Library thay vì setTimeout; beforeEach reset state; mock Date.now() với jest.useFakeTimers(); server.resetHandlers() giữa tests.
Phát hiện: chạy test file nhiều lần jest --testPathPattern=flaky.test.ts --runInBand --repeat 50; Playwright có --repeat-each.
CI retry logic (GitHub Actions retry): cảnh báo nếu test cần retry > 1 lần — không xem là solution, chỉ là detection mechanism.
Phân biệt thực tế: Stub cung cấp canned response không quan tâm đến cách gọi — jest.fn().mockReturnValue(user) để feed data cho SUT (system under test). Mock verify interactions — expect(emailService.send).toHaveBeenCalledWith(email) để đảm bảo behavior đúng. Fake có working implementation đơn giản hơn — in-memory repository thay vì real DB, fake email service lưu vào array.
- Hướng dẫn thực tế: dùng Stub khi test chỉ cần control input data (không care về side effects); dùng Mock khi hành động quan trọng phải xảy ra (email gửi, event emit, payment charge); dùng Fake khi cần nhiều interactions với dependency (5 operations trên DB) — fake in-memory repo nhanh và realistic hơn 5 mock calls.
- Over-mocking smell: nếu test cần mock > 3-4 dependencies, đó là dấu hiệu design issue (too many dependencies → god class).
- Test với real implementation khi có thể (fast enough, no external I/O) — integration > unit với mocks cho confidence.
Pitfall: mock quá nhiều → tests pass nhưng integration fails (mock không reflect thực tế API).
getBy throws khi không có (test fails fast); queryBy returns null (dùng để assert absent); findBy async (dùng cho sau API call) — ưu tiên getByRole > getByLabelText > getByTestId.
- Ba nhóm queries trong @testing-library/react với behavior khác nhau khi element không tồn tại: getBy: throw error ngay lập tức nếu không tìm thấy — dùng khi element phải có (fail fast, rõ ràng nhất). queryBy: trả về null nếu không tìm thấy — dùng khi muốn assert element không có:
expect(queryByRole('alert')).not.toBeInTheDocument(). findBy: trả về Promise, wait cho element xuất hiện (mặc định 1000ms, configurable) — dùng cho async operations như API calls. - Tương tự cho multiple elements: getAllBy/queryAllBy/findAllBy.
- Priority order (Testing Library khuyên dùng theo thứ tự ưu tiên): getByRole > getByLabelText > getByPlaceholderText > getByText > getByDisplayValue > getByAltText > getByTitle > getByTestId.
- Role-based queries
getByRole('button', { name: 'Submit' })là tốt nhất vì test user-visible behavior và verify accessibility. - Không dùng
getByTestIdcho mọi thứ —data-testidlà last resort, liên kết test với implementation details. waitFor pattern:await waitFor(() => expect(screen.getByText('Loaded')).toBeInTheDocument())— retry assertion cho đến khi pass hoặc timeout.
Playwright hỗ trợ storageState để save và reuse authentication state giữa tests.
- Pattern: globalSetup.ts chạy một lần trước tất cả tests — login qua UI hoặc API, save state: await context.storageState({ path: 'auth/user.json' }).
- Reuse trong playwright.config.ts: use: { storageState: 'auth/user.json' } — mỗi test bắt đầu đã authenticated, không phải login lại.
- Multiple roles: save different storageStates cho admin, regular user, guest — dùng projects trong config để chạy same tests với different roles.
- API-based auth nhanh hơn UI login: const response = await request.post('/api/login', { data: { email, password } }); await page.context().addCookies([...]) — avoid UI flakiness.
- Page Object Model (POM): class LoginPage { constructor(page) {...}; async login(email, pw) { await this.page.fill('#email', email); ... } } — encapsulate page interactions, reusable across tests, easy maintenance khi UI thay đổi.
- Fixtures trong Playwright: custom fixtures extend base test với pre-setup state — const test = base.extend({ loggedInPage: async ({page}, use) => { await loginUser(page); await use(page); } }) — elegant và composable.
- Avoid test interdependency: mỗi test phải independent, không phụ thuộc vào state từ test trước — dùng beforeEach để reset state.
Contract testing verify API contract (format, structure, behavior) giữa service provider và consumer mà không cần cả hai chạy đồng thời — critical trong microservices.
- Consumer-driven contract testing với Pact: Consumer viết tests định nghĩa expectations về provider responses (pact file); Provider chạy pact file để verify nó thỏa mãn consumer expectations.
- Pact Broker: central repository cho pact files, track which consumer versions are compatible với which provider versions.
- Can-I-Deploy tool: query Pact Broker để check nếu service version có thể deploy safely — 'can consumer v2 deploy if provider is v3?'.
- Tại sao quan trọng hơn E2E tests trong microservices: E2E tests cần tất cả services chạy đồng thời (expensive, slow, flaky); contract tests chạy independently per service — fast, reliable, pinpoint exactly which service broke contract.
- Breaking change detection: nếu provider thay đổi response schema, pact verification fail ngay trong CI trước khi deploy — không phải phát hiện trong production.
- Schema registry (Avro, Protobuf) cho event-driven architecture: schema evolution rules, backward/forward compatibility checks — contract testing cho async messaging.
- OpenAPI contract testing: Dredd, Spectral verify API implementation against OpenAPI spec — simpler than Pact nhưng ít powerful.
Mock verify interactions (assertions), Stub cung cấp canned responses, Spy observe real implementation, Fake có working implementation đơn giản — overcrowded mocks là code smell, thường báo hiệu SRP violation.
- Test doubles là các loại replacements cho real dependencies trong tests — Martin Fowler phân loại trong 'Mocks Aren't Stubs'.
- Dummy: placeholder object được pass vào nhưng không bao giờ thực sự được dùng — ví dụ pass null hoặc empty object để fulfill parameter requirement.
- Stub: trả về pre-configured responses cho calls — không verify interactions, chỉ provide canned answers; ví dụ stub database trả về fixed user object.
- Mock: pre-programmed với expectations về calls mà nó sẽ nhận — verify những interactions này xảy ra; jest.fn() là mock vì có thể assert toHaveBeenCalledWith(); test fail nếu expected calls không xảy ra.
- Spy: wrap real implementation, record calls — jest.spyOn() by default calls real method, ghi lại arguments; có thể override với mockImplementation khi cần.
- Fake: có working implementation đơn giản hơn real — fake in-memory database (Map<id, entity>), fake email service (lưu emails vào array thay vì gửi thật), fake message queue.
- Practical guidance: dùng Stub khi cần control return values; Mock khi muốn verify behavior (dùng đúng dependencies đúng cách); Spy khi muốn observe real implementation; Fake khi cần realistic behavior nhưng không thể/không muốn dùng real service.
- Overcrowded mocks là code smell: nếu test cần mock quá nhiều dependencies, code đang vi phạm Single Responsibility Principle — refactor code, không thêm mocks.
Coverage thresholds fail CI khi coverage rơi xuống dưới mức tối thiểu — enforced trong jest.config.js với coverageThreshold.
- Global threshold:
{ global: { branches: 80, functions: 80, lines: 85, statements: 85 } }— áp dụng cho toàn bộ codebase. - Per-file threshold:
{ './src/services/payment.ts': { branches: 95, lines: 95 } }— critical business logic cần coverage cao hơn. - Coverage collectors: Istanbul (mặc định Jest) vs V8 (native, nhanh hơn,
coverageProvider: 'v8'). - Exclude files:
coveragePathIgnorePatterns: ['/node_modules/', '/dist/', '.stories.']— không count generated files, stories, test helpers. - Branch coverage là metric quan trọng nhất — 80% branch nghĩa là nhiều if/else paths được test; statement/line coverage dễ inflate.
Pitfall: đặt threshold quá cao ngay từ đầu gây friction; bắt đầu thấp (60-70%) rồi tăng dần; không chase 100% cho toàn bộ codebase — UI components layout tests có coverage thấp không có nghĩa là nguy hiểm. --collectCoverageFrom: ['src/*/.{ts,tsx}', '!src/*/.d.ts'] đảm bảo files không có tests được tính vào coverage (không chỉ tính files được import bởi tests).
Test isolation đảm bảo tests không phụ thuộc thứ tự — dùng jest.resetAllMocks() trong afterEach, transaction rollback cho DB tests, tránh global state mutation.
- Test isolation đảm bảo mỗi test chạy độc lập — không phụ thuộc vào thứ tự, không share state với tests khác.
- Tại sao quan trọng: test order-dependent là flaky (pass individually, fail khi chạy cùng); shared state làm debug khó (failing test có thể do test khác gây ra).
- Kỹ thuật isolation: Module mocks:
jest.resetModules()trong beforeEach để reset module registry — cần thiết khi mock modules có side effects; Environment reset:jest.clearAllMocks()(reset call count),jest.resetAllMocks()(clear implementations cũng),jest.restoreAllMocks()(restore spies); Database isolation: transaction rollback sau mỗi test (Prisma:prisma.$transactionvới rollback trong afterEach), hoặc truncate tables; Test-scoped servers: mỗi test suite tạo Express instance riêng thay vì shared global app.--runInBandflag chạy tests sequential (không parallel) để debug isolation issues — nếu test pass với--runInBandnhưng fail parallel, đó là shared state bug. - Jest worker isolation: mỗi file chạy trong separate Node.js worker — file-level isolation tốt; within file mới cần manual isolation.
Pitfall: global variable mutations trong test không cleanup → leak sang test tiếp theo.
Viết E2E tests bền vững: Page Object Model để tập trung selectors, semantic locators (getByRole) thay CSS classes, trace-on-first-retry để debug CI failures, base fixtures để tránh duplicate login.
- E2E tests hay bị brittle (break khi UI thay đổi).
- Chiến lược viết maintainable tests: Page Object Model (POM):
class LoginPage { async login(email, pw) { await this.page.fill('[data-testid=email]', email); await this.page.fill('[data-testid=password]', pw); await this.page.click('[data-testid=submit]'); } }— encapsulate selectors và actions, một chỗ thay đổi khi UI thay đổi. Semantic locators:page.getByRole('button', { name: 'Submit' })vàpage.getByLabel('Email')bền hơnpage.locator('.btn-submit')— ít bị ảnh hưởng bởi CSS class changes. data-testid attributes: đặtdata-testidtrên key elements trong production code — explicit contract giữa dev và test. Avoid hardcoded waits: thaypage.waitForTimeout(2000)bằngpage.waitForSelector('[data-testid=result]')hoặcexpect(locator).toBeVisible()— Playwright auto-retry. Base test fixtures:test.extend({ authenticatedPage })pre-login trước mỗi test — không repeat login logic. Tracing on failure:trace: 'on-first-retry'trong playwright.config.ts — chỉ record trace khi fail, xem trace.zip trong Playwright Trace Viewer để debug CI failures không cần reproduce locally.
Pitfall: quá nhiều assertions trong một test — nếu một assertion fail, không biết context; nhóm related assertions theo user journey.