Mike Cohnβs wonderful book Succeeding with Agile talks about the Test Automation Pyramid, shown in the image above.
This pyramid is beautifully explained by Martin Fowler and I certainly canβt do even half as good a job as he can, so if youβre unfamiliar with the concept I suggest you read that post.
A key point is that unit tests should make up the bulk of your tests because they are cheaper to write and maintain, and because they are faster to run.
Recently though, I keep seeing and hearing from JavaScript folks who believe that unit testing is more trouble than itβs worth. There are smart, thoughtful front-end programmers who truly believe that end-to-end tests are the way to go.
So what happened to the humble unit test? Is the test pyramid still relevant?
Front-end frameworks make unit testing techniques less obvious
I think the main reason for lack of faith in the test pyramid is that front-end unit testing is fraught with danger. It can take a great deal of experience and confidence to be able to write a front-end unit test suite that is cheaper and easier to maintain than a set of UI-level tests.
Unit testing is proving harder and harder to do as we begin using frameworks that mesh together static data with behavior, like how React source files are a mixture of HTML and JavaScript.
Isnβt the front-end just the UI tip of the pyramid?
No.
Modern front-end applications involve user workflow, receiving and sending data through network requests, handling session state and data validation. For applications that involve content creation, thereβs plenty of business logic to contend with, too.
All of these lend themselves well to unit testing.
If youβre working in a modern JavaScript codebase, you would do well to structure it with a standard hexagonal architecture. Then unit testing becomes straightforward, and the test automation pyramid begins to make sense again.
Knowing what not to test: the declarative nature of the front-end
Broswer-based development (i.e. βthe front-endβ) is a lot of HTML and CSS mixed in with some JavaScript. Thankfully the HTML and CSS is static data, and unit tests arenβt generally meant for static data.
Unit tests really shine when youβre testing behavior, not simply repeating static information from the production codebase.
This applies to any framework you might be using, be it React or Vue or Svelte, or simply just plain JavaScript.
If you can create a very clean separation between your static data and your behaviour, then unit testing becomes painless.
Unfortunately, that separation isnβt very easy to do. You can read about my way of doing it in React in this blog post.
HTML and CSS can easily be verified by visual regression and golden master tools
If youβve got a QA team, they are, without a doubt, interested in verifying that your static HTML and CSS does what itβs meant to.
They can use visual regression tools for this exact purpose.
Jest has a mechanism called snapshot testing which does just this, and itβs quite nice in that it can run on individual components in a component tree. Smaller units reduces the brittleness of these tests.
Unfortunately, these tests are often written in the same test suite as your unit tests, and snapshot testing is promoted as a kind of replacement for unit tests, even though the tests serve a different purpose (they donβt test behavior) and arenβt necessarily a good fit for a unit test framework like Jest.
My take on why the original pyramid isnβt working
The assumption that unit tests are cheaper is no longer true simply because itβs harder to write unit tests.
Unit tests are only cheap if you have enough experience on your team to know how to write unit tests well.
What I observe from years of working with component-based frameworks is that people really struggle with unit tests.
If this is you, then donβt be pressured (by people like me!) into thinking youβve got to start writing unit tests. Instead, the onus is on us (people like me!) to help simplify unit testing practice. Until then, stick with whatever form of testing brings you most value.
Top comments (11)
I don't think there is a one-size-fits-all approach, when it comes to testing.
If you are writing utility functions, those should definitely be covered with unit tests.
If they are useful, you might want to make them a separate library, and not keep them with your web projects, however.
If you are writing a JS framework (Vue, React, Angular, etc.), you should definitely write unit tests to cover it.
If you are leveraging a framework, you shouldn't be writing tests that the framework does what it's supposed to do, so you'll probably end up writing more integration tests.
I highly recommend Kent C. Dodd's *Write tests. Not too many. Mostly integration. blog post / talk.
To me, that sounds more like integration tests than unit tests.
Whatever is back-end / API will be tested by unit tests in
pytest
(as I use Django for my back-ends - maybe I'll eventually take the plunge with something like Node or Express).I guess you can mock network requests, and call it a unit test... lines get kind of blurry at that point :D
Thanks for the reply. Thereβs a lot in this so perhaps I can just pick up a few things:
The distinctions between unit & everything else is possibly more useful than other distinctions. I think the key distinction of βunitβ is that they are isolated and so are protected from change / breakage. Non-unit tests are more brittle in the sense that because they execute a larger surface area, they are more likely to require change as the codebase changes. This is why we think of unit tests as cheaper.
Itβs the guidance of preferring integration tests that I am challenging in this post. Iβm suggesting that this advice came about not because unit tests donβt make sense on the front-end, but because they often prove extremely difficult for people to get right. I.e. they end up mocking everything, or writing convoluted tests, or their tests get in the way of refactoring, and so on.
On the point of frameworks -- I exist in the camp of people that believes that we should always βminimizeβ frameworks as much as possible. In the case of React that means try to suck as much code out of React components as possible. That way you avoid lock-in and unit testing becomes simpler.
They definitely are. To me, the key reason to write tests is because they add value. If I have code like:
writing a test with mocked data feels like I am testing that Vue.js isn't broken and that
v-for
works. That's Vue's responsibility to test, and I won't bother - it doesn't add value to me.I may write a test that mocks an API call to make sure it renders everything correctly, but again, that, to me, is an integration test.
I'd be curious to see what you consider a good unit test for frameworks. (I'll be sure to look through your Svelte testing series, even though I am not currently interested in learning another framework - maybe I'll find an answer there.)
I can respect that position. As I'm not good at design, and don't care to spend 2 months writing apps that work well on OSX, and iOS, and Safari, and IE11, etc., I personally like frameworks that offer a lot of abstractions, and let me focus on features. I love working with Quasar (based on Vue). But that's a personal choice, and I don't think there is one good answer - we've all got our own strengths, weaknesses, and preferences.
I think Unit tests are not suited for modern frontend development. I'm a backend developer and doing Unit testing in RoR is simple and makes all the sense as we have classes and instance methods.
In modern frontend frameworks, it looks more obvious to do integration and snapshot testing and this has been a good experience my coworker has had in one of our projects.
I also think is important more frontend developers start/continue doing testing BUT the testing the helps them do their job and doesn't add trouble to their daily tasks.
You may be aware of this talk between DHH, Martin Fowler and Kent Beck, which came about as a result of confusion about how to unit test Ruby on Rails.
Frameworks do make it harder to unit test, but it doesnβt make it impossible.
With Ruby on Rails a key example is Active Record. This pattern makes it difficult to write βunitβ tests because itβs hard to test an Active Record model without interacting with a real database resource. For some people this breaks a rule about what a unit test should be. They then replace Active Record with something like the repository pattern. For people like myself, we just shrug our shoulders and use the database, and still call it unit testing. Sure, my tests arenβt as fast as they might otherwise be, but Iβm still gaining all the other benefits of unit tests over higher-level tests.
On the front-end... I think your use of the word simple is important. People donβt believe that unit testing UI code is simple. In my opinion, thatβs why they donβt do it. And since I know that Iβm having a lot of success with unit testing on the front-end, Iβm trying to share that knowledge with others.
I'm in the group of people who think doing unit testing in frontend isn't simple π
One of the reasons I think people feel that way is that it isn't obvious what to unit test in frontend world. Do we test styles? Do we test structure of HTML? Of course we should be testing JS functions but what about event handlers? Probably we've been doing it wrong.
And even though your article answers those questions, doing unit tests in the way you mentioned isn't as easy as frontend devs would like/hope.
Then I hope to see your content about this topic π
BTW, I wasn't aware of the DHH, Fowler, and Beck video. Thanks for sharing ππ½
This is why I have started to love React Testing Library, I am forced to test UI behavioural changes only when testing my React components and pages. It stops a lot of the complexity and lets you test.
Good call. Iβm curious: what were you using before that?
In my experience the distinctions of the test pyramid are not great given the fractal nature of well designed systems. I find it much easier follow a general guide of writing very few expensive tests and very many cheap tests. That is also more understandable to business people.
I generally agree with this, especially that the model doesnβt speak to business people.
Models like the test pyramid tend to lose their usefulness once we gain enough practical experience with the subject area. I still think the test pyramid is something we should all learn and understand, if only to get across the point that there are ways to judge the cost of a test before youβve even written it.
Nice article! I think frontend testing has become quite blurred and more than other platforms, the different testing levels have much more specific uses.
There is no point unit testing a simple UI component, for example. A visual regression test (I don't mean a jest snapshot of the html, I'm talking actually capturing the css from the browser) is far more valuable.
For more complex components, I make a point of extracting any logic out and unit testing it in isolation. Then writing higher level integration tests to ensure the component as a whole works as expected.
Something that gets glossed over quite a lot is that react-testing-library is mostly built for integration testing. I don't think this is stated enough. This is why unit testing a component is quite awkward.