Skip to main content

Search...

UI Testing with Mocks: Test the Frontend Without a Backend

UI testing with mocked API responses checks the frontend as a full app, no backend needed. At Volkswagen, 280 tests run in under three minutes.

• • Updated: • 11 min read
Cover of the expert talk on 'UI Testing with Mocks: Test the Frontend Without a Backend' with Felix Wunderlich and Richard Seidl.

UI testing here means testing a web application as a whole from the user’s perspective, with backend requests replaced by predefined mock responses. The frontend runs isolated in the browser, with no real database or backend behind it. That lets teams check user flows quickly, reliably and in parallel, without the overhead of full end-to-end tests.

Key Takeaways

  • In Felix Wunderlich’s team, UI tests with mocked API responses have replaced both component tests and most end-to-end tests, and 280 of them run in under three minutes on a 16 GB VM.
  • Testing the frontend in isolation against defined mock responses covers the whole application from the user’s perspective, without spinning up a backend, a database or test data management.
  • Playwright can intercept HTTP requests in the browser context and answer them right away with prepared responses, which makes parallel runs and cross-browser checks at different viewport sizes possible.
  • The biggest setup risk isn’t the test approach itself but a lack of centralization: CSS selectors, request definitions and response fixtures need to live in one place from day one, or every small UI change drags dozens of test updates behind it.

Why Component Tests and End-to-End Tests Fall Short for Frontends

Frontend testing often gets stuck between two worlds, and neither one fits. On one side are component tests: isolated, small, usually built like unit tests. On the other are end-to-end tests: complete, but slow and expensive to set up. UI testing with mock responses fills the gap between them, and that gap holds exactly what matters most: the application the way a user actually works with it.

A component test mounts a single component into a small DOM with Jest, Mocha or a similar tool and checks it on its own. Sometimes there are combined tests on top that cover an entire form. The trouble starts when the UI gets reworked. As soon as a component is moved or reassembled, whole rows of tests break that nobody wanted to touch.

End-to-end tests have the opposite problem. They run against a dockerized environment with its own database and its own test data management. That takes effort. Covering individual parts of the UI or error cases there isn’t worth the cost. And if the last step fails after a ten-minute run, you start all over again.

Testing the Frontend as a Whole Application, in Isolation

The approach that closes the gap tests the frontend as a complete application from the user’s perspective, without starting the real backend. The application runs in a browser context like the ones Playwright, Cypress or TestCafe provide. Inside that context, you can intercept the requests that would otherwise go to backends, APIs or databases.

Instead of starting the real system, the test swaps the answers for predefined mock responses. The frontend thinks it’s talking to the real system, but it runs fully encapsulated. That way you test the whole application as a user would, not just fragments of it.

Felix Wunderlich introduced this approach at Volkswagen’s Software Development Center in Wolfsburg, where his team builds software for car repair shops. The technical side with Playwright is simple: in the test, you define for a route that it returns a specific mock response to a specific request. For a working frontend, that’s all you need at first.

“If I test my frontend as a real application from the user’s perspective, in isolation, and on top of that in parallel and fast, then I basically have every benefit I can think of.”

(Felix Wunderlich)

Why Clicking Through the Whole Flow Is an Advantage, Not a Detour

The test operates the application the way a user would, including every step that comes before. In a wizard with five steps, the test really does fill in all five, even if it only wants to check the form in the last one. At first glance that seems counterintuitive, almost wasteful.

The payoff is in the application state. Walking through the wizard step by step makes sure the frontend’s internal state stays valid along the entire chain. That spares you the fiddly work of setting artificial intermediate states and the mocks that go with them.

Validations, different outcomes, outgoing requests in the last step: all of it can be scripted and tested individually. It’s surprisingly fast, because the earlier steps aren’t the expensive part.

How UI Tests Become Fast and Stable Instead of Slow and Flaky

Slowness and flakiness are the standard objections to UI tests. With mocked responses, both shrink considerably. Since no real backends respond, many tests can run in parallel.

The numbers from practice: 280 UI tests run in under three minutes on a virtual machine with 16 GB in the pipeline. For that many tests, that’s fast.

Stability actually improves. The tests run across different browsers and at different screen sizes. A bug that only shows up in a certain Firefox version gets caught in testing, not in production. What would otherwise surface late and by chance becomes explicit.

What the Approach Does for Quality

The main gain is confidence with every change. After any change, however small, you know the UI as a whole still works from the user’s perspective. You can improve the frontend’s code quality, run all the tests at the end and trust the green result.

With isolated component tests that wasn’t possible. There, if in doubt, the E2E test ran for ten minutes, the last step failed and everything started over. The switch put an end to that loop.

As a consequence, the team no longer has any component tests. During the migration, every end-to-end test that wasn’t relevant to the user flow or the overall system was pulled out and turned into a UI test.

Comparison: Three Kinds of Frontend Tests

Test TypeScopeSpeedWeak Spot
Component testSingle component in the DOMFastBreaks when the UI is reworked, cut off from the real flow
End-to-end testEntire system with backend and databaseSlowComplex setup, not worth it for detailed cases
UI test with mock responsesEntire application, backend mockedFast, parallelizableSetup effort at the start, duplication with shared components

The Drawbacks of UI Testing with Mocked Responses: Duplication and Setup

Two areas turned out to be painful. The first is duplication with shared components. A button with an asynchronous operation, a loading spinner and a green checkmark can appear in three places in the user flow. Then three tests in three places check the same thing.

That clashes with the wish not to write anything twice. In one more complicated case, the team resolved it by splitting the components apart. Still, the fair question remains in the team whether the same test really needs to exist three times.

The second area is maintaining the setup. When one CSS selector changed, dozens of tests had to be updated. That led to a clear lesson for how to build it.

How to Structure the Setup So Changes Don’t Break Everything

Before you start, look closely at your UI and write down what it talks to in the background. That overview is the foundation for everything else.

Then pull together in one place what would otherwise be scattered across the tests:

  • The CSS selectors of the UI elements, in one place
  • The requests, in one place
  • Reusable response builders and test data builders as building blocks

This central setup is a lot of work at the start, and not the most enjoyable kind. The benefit shows later. If a backend API changes and sends a different response, you update the response builder in one place. As long as the change doesn’t affect the UI, everything keeps running as before.

For an existing product with existing component and end-to-end tests, the setup effort is tough at first. It’s still well worth it.

An Experiment Beats a Debate About Principles

The idea caught on because it started as an experiment, not a mandate. The pain was felt every day, by everyone on the team. Migrating from too many end-to-end tests to UI tests was manageable in terms of effort. So they tried it.

An experiment is allowed to fail. If it does, you either live with the old pain or look for another solution. Here it worked, and that successful experiment is exactly what won the whole team over.

The environment helped. At the Software Development Center, teams own their products and try to build the best they can. If you want to try something new, there’s room for it.

What Comes Next

More tests eventually mean more runtime. With 500 UI tests over several years of product development, even these get slower. Just throwing a bigger machine into the pipeline isn’t a satisfying answer for an engineer: you’re throwing money at the problem instead of solving it.

The obvious direction is sharding, splitting the tests up and pushing parallelization further. How far that can go is still open, but there’s room.

The bigger lever is spreading the approach. It’s meant to be used in more products, and in the long run UI testing could be accepted across several tools. Many colleagues like the idea but are attached to their favorite tools. If the concept gets accepted independently of the tool, it could become the new standard way to test a UI.

Frequently Asked Questions

What Happens to Component Testing When the User Interface Is Redesigned?

They break left and right. Component testing uses Jest, Mocha, or a similar tool to place a single component into a small DOM and test it in isolation. If the component is moved or reconfigured, tests that have nothing to do with the actual change fail. They don’t cover the actual user flow anyway.

Do you always need an environment with a backend and a database for frontend testing?

No. If you intercept the requests in the browser context and replace them with predefined mock responses, you can test the application from the user’s perspective: without a real backend, without a database, and without test data management. The frontend believes it is communicating with the real system, but it runs in an isolated environment. It is precisely the setup of the complete environment that makes end-to-end testing time-consuming and cost-inefficient for individual error cases.

How fast do UI tests with mocked responses run in practice?

In Felix Wunderlich’s team, 280 UI tests run in under three minutes on a virtual machine with 16 gigabytes of memory. This is made possible by parallelization: since no real backends are responding, many tests can be executed simultaneously. The usual objections to UI tests (slowness and flakiness) therefore lose their weight.

Can a team completely replace component testing with UI tests?

In the Volkswagen team described above, that is exactly what happened: There are no longer any component tests there. During the migration, all end-to-end testing that was not relevant to the user flow or the overall system was also removed and converted into UI tests. What remains are E2E tests for the system-wide view and UI tests for everything else.

Should a test set intermediate states directly, rather than clicking through all the steps of a wizard?

It’s better to click through. For a wizard with five steps, the test fills out all five, even if only the form in the last step is being tested. This ensures that the internal application state remains valid throughout the entire chain, eliminating the need to tinker with artificially set intermediate states and their associated mocks. The preceding steps aren’t the costly part.

What’s the biggest pitfall when building a test suite like this?

Lack of centralization. When a single CSS selector changed in production, dozens of tests had to be updated. That’s why CSS selectors, requests, and reusable response builders should all be stored in a single location from the very beginning. If a backend API then changes its response, you adjust the response builder just once, and as long as the UI remains unchanged, everything continues to work.

Do split components lead to duplicate tests?

Yes, and that’s the most unpleasant aspect of this approach. A button with an asynchronous operation, a loading spinner, and a green checkmark can appear in three places in the user flow, meaning three tests check the same thing. In a more complicated case, this was resolved by separating the components. The question of whether the same test is needed three times remains valid.

What to Do When the Number of UI Tests Grows and Run Times Increase?

Sharding is the obvious solution: split up the tests and further increase parallelization. With 500 UI tests spread over several years of product development, even a fast run becomes sluggish. Simply adding a more powerful machine to the pipeline doesn’t solve the problem, it just buys time. It remains to be seen just how far sharding can be taken.

Share this page