Skip to main content
Test Double company logo
Services
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say Hello
Test Double logo
Menu
Services
BackGrid of dots icon
Services Overview
Holistic software investment consulting
Software Delivery
Accelerate quality software development
Product Management
Launch modern product orgs
Legacy Modernization
Renovate legacy software systems
Cycle icon
DevOps
Scale infrastructure smoothly
Upgrade Rails
Update Rails versions seamlessly
Technical Recruitment
Build tech & product teams
Technical Assessments
Uncover root causes & improvements
Case Studies
Solutions
Solutions
Accelerate Quality Software
Software Delivery, DevOps, & Product Delivery
Maximize Software Investments
Product Performance, Product Scaling, & Technical Assessments
Future-Proof Innovative Software
Legacy Modernization, Product Transformation, Upgrade Rails, Technical Recruitment
About
About
About
What's a test double?
Approach
Meeting you where you are
Founder's Story
The origin of our mission
Culture
Culture
Culture & Careers
Double Agents decoded
Great Causes
Great code for great causes
EDI
Equity, diversity & inclusion
Insights
Insights
All Insights
Hot takes and tips for all things software
Leadership
Bold opinions and insights for tech leaders
Developer
Essential coding tutorials and tools
Product Manager
Practical advice for real-world challenges
Say hello
Developers
Developers
Developers
Testing

How modern frontend teams approach automated testing

Modern frontend testing doesn't need to be overwhelming—focus on two types of tests that deliver real value: co-located unit tests using tools like Vitest and Testing Library, plus targeted E2E tests for critical user flows. This practical approach helps product teams move fast while maintaining confidence, without getting bogged down in testing theory or arbitrary coverage metrics.
Robert Komaromi
|
July 30, 2025
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Introduction

If you’re a developer, tech lead or product owner new to testing in the frontend world, or need a fresh perspective on how it’s done, this blog post should shed some light on how modern, frontend product teams may bootstrap their testing setup, and how automated testing is an important part of the product development lifecycle. Whether you're adding tests to a legacy system or starting on a greenfield product, you'll learn how to create a testing setup that serves your team's real needs.

This post aims to demystify automated testing in real-world frontend environments. You'll get a look at how experienced teams approach unit and end-to-end testing, where TDD fits in (or doesn’t), and how tools like Vitest, Cypress, Testing Library, and MSW can help strike the right balance between speed, coverage, and confidence.

The overwhelming automated testing landscape

In the world of web development, automated tests usually come in three flavors: unit tests, integration tests, and end-to-end (E2E) tests. There are other categories and subcategories of tests too, of course, like mutation tests, penetration tests, contract tests, and more.

Along with the various types of tests, we also have various techniques for organizing and utilizing them, chief among them being TDD (test-driven development) and its red-green-refactor process.

You've heard of the testing pyramid, the testing trophy, and maybe even the testing honeycomb!

With all these testing ideas floating around, how do you know where to spend your energy? Where do you start? Is TDD dead?!

Finding our bearings within automated testing

I've been working for years building products from a user perspective—making UIs that look and behave in pleasant ways. Once I have a feature or product working well, I want it to keep working well.

Before automated testing tools took over the software industry, we had dedicated testers go through scripts and manually verify that the software did the right thing. This was extremely slow and costly.

The automated tests that many of us write are only checks; we don't discover new information about our applications, we verify only that they work within the constraints we apply to them. Running our test suite multiple times will not reveal new bugs or defects.

These are some of the problems automated tests help solve. You want checks to make sure your software works as intended, and you want those checks performed as efficiently as possible.

But the software industry has evolved significantly over time, and tests are no longer something you run only at the end of your development cycle to verify things.

Tests come in all shapes and sizes, and thinking too much about that in the beginning can create more distractions than focus. You should consider what unit tests are, how fast they should run, and if you even need them, but getting bogged down on what "shape" your testing suite needs to have, or whether your unit tests are only unit tests if they run under 200ms, are not key things to focus on when starting a new project or adding automated tests to a legacy system.

As Martin Fowler notes in his article on test shapes, when asked how to define "unit test," one test expert replied: "in the first morning of my training course I cover 24 different definitions of unit test." This has been the state of affairs for decades, and it highlights why getting caught up in definitions can be counterproductive.

Why introduce automated tests?

There are many reasons for writing automated tests. They can be used to:

  • Ensure features you've built continue to work as originally intended
  • Prevent previously identified and resolved bugs from being reintroduced
  • Act as documentation for how the code should behave
  • Guide the developer in writing the underlying code
  • Serve as a learning playground to understand unfamiliar code, APIs, or libraries by exploring their behavior and discovering their limits

Tests give you confidence that your code is working correctly and is maintainable, and will save you time by allowing you to move faster without breaking things. As Kent C. Dodds puts it, it's all about getting a good return on your investment where "return" is "confidence" and "investment" is "time."

Think about the benefits you and your team will gain from automated testing, and apply them when building out your test suite.

What can a modern testing setup look like?

After working on a few frontend product teams, here's a setup that I've found works well:

Create two types of tests: unit tests and E2E tests. I sometimes just call them "Vitest or component tests" and "Cypress tests." Popular alternatives are Jest and Playwright, respectively. The names aren't important. Don't get bogged down thinking that unit tests should all run under some arbitrary time limit or that E2E tests need to have absolutely nothing mocked out or they can't be considered "true" E2E tests.

Testing units

For me, "unit" tests all use Vitest and are co-located with the code they test, either right beside the file/component or in an adjacent __tests__ directory, named with something like a .test.ts extension. These test files can contain many individual tests, and each test should generally have a single reason to change—a single feature that might cause it to fail. This keeps the feedback loop tight, making it easier to identify and fix the source of the test failure. If you're adding a new feature to an existing component, this makes it easy to find the test file, skim over it to get an idea of what it can do (this is how it can act as API documentation), and then write your feature while you add the test for it. By writing your tests along with your component (either before or after), you'll get into the habit of creating components that are easy to test. Easy-to-test components are ones that don't have complex dependencies and can be more easily reasoned about by their "public APIs"—the props and rendered HTML—without having to untangle ten different responsibilities.

To help facilitate good unit tests, use Testing Library and Mock Service Worker (MSW). These tools will ensure you're testing the app from a user's perspective, avoiding reaching into component internals or over-mocking component dependencies.

Does every single component and every single file need an associated test file? Absolutely not. If you have an age display component that displays a user's age given their date of birth, and that component is only used in a user info component, it's probably sufficient to have a single test for the user info component that checks the displayed age instead of having multiple tests for the age component, the user info component, and the age calculation function. As your product evolves and your team matures, you'll find the right balance between number of components and associated tests.

Why unit testing helps frontend product teams:

Co-located tests using consistent patterns like __tests__ folders and .test.ts files make it faster to onboard new developers and easier to maintain existing features. Using Testing Library and MSW creates a predictable testing environment that helps with both onboarding and long-term maintenance.

TDD

The TDD adherents in the audience may have raised their eyebrows earlier when I said that you can write your tests after you’ve implemented your component. If you find TDD works well for you, I’m not going to convince you to give it up. But when it comes to writing components on the frontend, I’ve found it’s difficult to start with a test when components are primarily user-driven, visual entities.

Today, you have IDEs with built-in LSP (Language Server Protocol) support, integration with formatting and linting tools, static type checking with TypeScript, and code-generation abilities to quickly scaffold out not only component boilerplate, but associated test files as well. It’s much easier now than ever to start writing the test after you’ve started a basic outline of your component:

  • You will have to play with the component, see if it works well, is usable, accessible, responsive, matches designs, and doesn’t produce any warnings or errors in the dev tools console of your browser. This is all extremely valuable feedback that will lead to significant refactors to your component as you build it up to meet requirements, and which an initial unit test will likely not help you with.
  • IDEs do a lot of hand holding, so take advantage of it. If you start with a unit test, you’ll instantly run into warnings about missing files, at the very least. If you already have a component, then you have a foundation to iterate more quickly: your IDE will correctly pull in the imports for your component and provide feedback via IntelliSense or code completion.

Keep in mind, after your component and test are ready for review, you should do some manual mutation testing to ensure there are no false positives (tests that continue to pass when the component is actually broken). Some components can have loading states and complex async data flows that are difficult to decompose, and your test may assert against a transient state that doesn’t reflect a real user experience. Or, you may have been too loose with your selectors and accidentally asserted against the wrong element on the screen. Purposefully break your component and verify your tests fail in meaningful ways.

Why TDD helps frontend product teams:

Writing tests after initial component development aligns with how frontend work actually happens—you need to see and iterate on visual components first. This approach lets you leverage modern IDE tools to move faster while still getting the safety net of automated tests.

End-to-end

Moving up, we have our E2E tests, or if our team is using Cypress, I just call them Cypress tests. Ideally, these tests will run a production build of the entire app against a production-like database. But sometimes all you have is the frontend production build, and that's okay too (you'll just have to end up mocking the network responses, but luckily you should be able to reuse your MSW setup).

Cypress tests are great—they're easy to write as you don't have to worry about individual components. You can even use browser extension tools that will record your steps through the app and have the Cypress test generated for you! These tests should focus on the business-critical flows that real users go through. You generally don't want to spin up a Cypress test just to check that the user's age is displayed correctly. You'd use it to do things like go through the entire onboarding flow to ensure that users are not blocked from gaining access to your product in some fundamental way.

Why is this? Cypress tests have two main issues: they tend to be slow (as they usually require spinning up a production-like version of your entire app), and they're more prone to flakiness (since you have less control over the browser environment).

You'll intuit from this that, because E2E tests are easier to write but slow, you'd write tests for potentially long user flows. Since there are fewer large features like this versus small features like the age display example, you'd naturally have fewer of these tests. And since they're slow, you'd run them when you've finished a feature, not after every change. Conversely, unit tests are "cheap"—they run fast and are easier to iterate on since they are scoped to smaller units. If a unit test fails, it’s much easier to narrow down the source of the failure. So you'll end up having more of them and generally run them as you're developing.

Why end-to-end testing helps frontend product teams:

E2E tests act as insurance for your most important user journeys—the ones that directly impact revenue and user satisfaction. By focusing these slower tests on critical flows like onboarding or checkout, you catch business-critical bugs while keeping development velocity high.

Going beyond just automated testing

Gone are the days when testing was purely manual, but manual testing is still a key ingredient in healthy software teams. A healthy, agile, cross-functional product team should have QA engineers who explore the product and review and write tests themselves. Despite the myriad benefits of automated testing, it's not a silver bullet, and 100% test coverage does not ensure bugs will not appear. We need to play with the software we write. It’s important to understand that real testing requires dedicated effort and is part of a healthy software process.

Yes, a mature software team will have a CI/CD pipeline with automated tests, but they should also have development and staging servers to allow developers and QA engineers to experiment with the app.

As Justin Searls wisely notes: "People love debating what percentage of which type of tests to write, but it's a distraction. Nearly zero teams write expressive tests that establish clear boundaries, run quickly & reliably, and only fail for useful reasons. Focus on that instead."

Resources

  • The Testing Trophy and Testing Classifications
  • On the Diverse And Fantastical Shapes of Testing
  • Breaking Up With Your Test Suite

‍

Related Insights

🔗
How to stop hating your tests
🔗
Please mock me
🔗
Please don't mock me
🔗
Why mastering testing is hard: The necessary and sufficient rule

Explore our insights

See all insights
Developers
Developers
Developers
C# and .NET tools and libraries for the modern developer

C# has a reputation for being used in legacy projects and is not often talked about related to startups or other new business ventures. This article aims to break a few of the myths about .NET and C# and discuss how it has evolved to be a great fit for almost any kind of software.

by
Patrick Coakley
Leadership
Leadership
Leadership
Turning observability into a team strength without a big overhaul

By addressing observability pain points one at a time, we built systems and practices that support rapid troubleshooting and collaboration.

by
Gabriel Côté-Carrier
Developers
Developers
Developers
Why I actually enjoy PR reviews (and you should, too)

PR reviews don't have to be painful. Discover practical, evidence-based approaches that turn code reviews into team-building opportunities while maintaining quality and reducing development friction.

by
Robert Komaromi
Letter art spelling out NEAT

Join the conversation

Technology is a means to an end: answers to very human questions. That’s why we created a community for developers and product managers.

Explore the community
Test Double Executive Leadership Team

Learn about our team

Like what we have to say about building great software and great teams?

Get to know us
Test Double company logo
Improving the way the world builds software.
What we do
Services OverviewSoftware DeliveryProduct ManagementLegacy ModernizationDevOpsUpgrade RailsTechnical RecruitmentTechnical Assessments
Who WE ARE
About UsCulture & CareersGreat CausesEDIOur TeamContact UsNews & AwardsN.E.A.T.
Resources
Case StudiesAll InsightsLeadership InsightsDeveloper InsightsProduct InsightsPairing & Office Hours
NEWSLETTER
Sign up hear about our latest innovations.
Your email has been added!
Oops! Something went wrong while submitting the form.
Standard Ruby badge
614.349.4279hello@testdouble.com
Privacy Policy
© 2020 Test Double. All Rights Reserved.