All jobs / Full-stack developer
Testing interview questions
Testing questions a Full-stack developer can be asked, from first job to senior. Open one to see the strongest answer and why it works.
You built a feature that touches a React screen and two API endpoints. Which tests do you write, and where?
The strongest answer: Most of it in fast unit tests on both halves, a smaller layer of integration tests around the API and the database, and one or two end-to-end tests for the journey that must never break
Why it works: Right — many cheap tests, few slow ones, and the slow ones reserved for the journeys you cannot afford to break.
- Many fast tests, few slow ones.
- Unit-test the logic on both halves.
- Integrate where API meets database.
- End-to-end only the critical journey.
You are writing your first tests for a POST endpoint. What do you actually assert?
The strongest answer: The status code, the shape and key fields of the response, that the record really changed in the database, and that bad input comes back as a validation error rather than a 500
Why it works: Exactly — status, body contract, persisted effect, and the unhappy path. Those four are what a consumer depends on.
- Assert the contract, not the code path.
- Status code and body shape.
- Check the data really changed.
- Test the invalid input too.
How do you keep the real API out of a component test without the test drifting from the real thing?
The strongest answer: Intercept at the network boundary — a request mock such as MSW returns realistic responses, the component runs its own fetching code, and the same handlers cover empty and error responses
Why it works: Right — mocking the network keeps the component’s real data layer under test, and one set of handlers covers success, empty and failure.
- Mock the network, not your own code.
- Keep the real fetching path under test.
- Share handlers across tests.
- Cover empty and error responses.
Your service runs a query with a join and a couple of filters, and the team is arguing about how to test it.
Do you test this against a real database or against a mocked repository?
The strongest answer: Against a real instance of the same engine, started for the test run and thrown away — a mocked repository only proves your own assumptions, never that the query returns the right rows
Why it works: Right — same engine, disposable instance. Joins, constraints, types and migrations are exactly what this test should exercise.
- Test queries against the real engine.
- Start it per run; throw it away.
- Substitutes hide dialect bugs.
- Never point tests at shared data.
How do you test the parts that are not a request — an incoming webhook and a nightly job?
The strongest answer: Call the handler directly with a recorded payload, signature check included, and replay it to prove it is idempotent; run the job’s function against a seeded database and assert the rows it changed
Why it works: Exactly — the schedule and the delivery are infrastructure; the logic behind them is ordinary code you can call with real payloads.
- Separate the trigger from the logic.
- Call handlers with recorded payloads.
- Replay one to prove idempotency.
- Assert the rows, not the schedule.
Someone deleted a required field and merged because the unit tests still passed.
What should CI do?
The strongest answer: Fail the build on a breaking OpenAPI diff unless the PR labels an intentional version bump — additive changes pass
Why it works: Exactly. The spec is a contract test. Breaking should be loud and opt-in, not a surprise in the iOS build next week.
- Breaking OpenAPI = red CI.
- Additive = green.
- Intentional breaks bump a version on purpose.
- Docs generation is not a compatibility check.
The suite is green on one worker and red across three shards; every spec signs in as the same demo user.
The end-to-end suite passes alone and fails when it runs in parallel. What is wrong with the test data, and how do you fix it?
The strongest answer: Each test creates the data it needs through the API under unique identifiers, and its own account where it can, so nothing is shared; whatever is left behind is namespaced per run and cleaned up
Why it works: Exactly — isolated data per test is what makes parallel runs safe, and a unique prefix makes leftovers easy to find and delete.
- Every test owns its data.
- Create it through the API, uniquely named.
- Its own account where possible.
- Namespace per run; clean up after.
Backend unit tests are green. The SPA is red on the next deploy because a field went optional to required.
How do you stop the API from breaking the UI in CI?
The strongest answer: Consumer-driven contract tests (Pact or a generated-client build) so an API PR fails if it would break a real caller — not only if its own unit tests pass
Why it works: Exactly. The join needs an automated consumer. Pact, OpenAPI-generated clients, or a tiny real request suite — something the API CI runs.
- A consumer test in the API pipeline.
- The UI (or mobile) owns the expectations.
- Breaking the expectation fails the PR.
- Chat is not CI.
Every pull request wants the browser suite, and staging is also where the product team demos.
Where should the end-to-end suite run in CI — against shared staging, or against a stack built for the run?
The strongest answer: An ephemeral stack per pull request — the build under test, its own database, its own seed — and a smaller suite against staging on a schedule to catch environment and data drift
Why it works: Right — per-run isolation makes results trustworthy and parallel, and the scheduled staging run still catches what an isolated stack cannot.
- Isolate the stack per run.
- Build under test, own database.
- Keep a scheduled staging run.
- Never run write suites on production.
Every dashboard is green, and a customer emails to say sign-up has been broken since the morning.
What would you run against production itself, and what must such a check never do?
The strongest answer: A scripted journey — sign in, load the dashboard, complete the key action — run every few minutes from outside and alerting on failure or latency, on a dedicated test tenant, never real customers or payments
Why it works: Exactly — dashboards say the parts are alive; a synthetic journey says the product works, and the test tenant is what makes it safe.
- Watch the journey, not the process.
- Run it from outside, on a schedule.
- Alert on failure and on latency.
- Dedicated tenant; never real money.
A web client and an API, a forty-minute suite that fails often, and a team that re-runs it until it is green.
You inherit a slow, flaky suite the team has learned to ignore. What is your strategy for the next quarter?
The strongest answer: Quarantine the flaky tests on a deadline to fix or delete, move coverage down into fast unit and contract tests, and keep a short end-to-end set for the journeys that earn money
Why it works: Right — a suite people trust is the goal, and speed plus honesty about what each layer buys you is how you get there.
- Trust first, coverage second.
- Quarantine flakes with a deadline.
- Push tests down the pyramid.
- Keep end-to-end short and meaningful.