DEV Community

Discussion on: The religion of test-driven development

0916dhkim profile image
Donghyeon Kim

Thanks for the article. Have you tried TDD in frontend development? I'm working on a React project right now, and sometimes it gets difficult to determine what to test. Also, splitting the UI into independent units is not always trivial. I would like to get some advice for writing testable frontend code.

frontendphil profile image
Philipp Giese Author • Edited on

Hey there! In fact, I'm mostly working with client code. Testing library is a great tool to write tests for frontend code. It's designed in a way that makes you "use" your app much like your users would. This helps you to avoid testing implementation details. That in turn helps you write fewer brittle tests.

I developed a habit to care about component decomposition at the latest state. I start with a test and build out a component. Once it becomes too large I split out smaller components. The great thing is that I don't necessarily have to move the tests into new test suites. Sometimes they only make sense in a larger setup. I've gotten used to thinking about UI tests as integration tests only. Unit testing UI components might sometimes make sense. However, if you start to mock everything that is interesting what are you actually testing?

0916dhkim profile image
Donghyeon Kim

It makes better sense now. I, too, mostly use Testing Library with Jest, but my problem has been that I always tried to make my tests smaller. Let me try writing more integration tests than unit tests and see how it goes. Unlike your workflow, I usually create a small independent component with its unit tests and put the new component inside existing components. I guess my tendency to write smaller tests made them more "brittle."

Anyway, here are two objectives I think about when I write my tests:

  1. Does the component reflect the state correctly?
  2. Do user interactions have correct consequences?

1 is easy to test, but it can be difficult to setup when the state is external (not props) or complicated. I mock external data.

2 is more tricky to test IMO. Many side-effects must be mocked (e.g. data fetching & url redirection). Also, querying for HTML elements is not trivial; I often end up with parent <div> when I actually wanted to select the child <input>. Querying is difficult for me because I am writing my tests for components which do not exist yet. Today, I wrote a test for a React form that goes like this: "fill out two text fields, select an option from a dropdown, press a button then check if the form sends a correct request." I spent more time troubleshooting getByRole() calls and fireEvent calls than implementing the form.

What I love about TDD is the confidence it gives me during development process. Before implementation, I have a firm objective to aim for, and after implementation, I am assured that my implementation is tested; however, writing solid tests has been a real challenge for me when it comes to frontend. Maybe it will be alright after I get more used to ARIA roles and web in general.

Thank you for your input. Now I feel like I have a better strategy to try out.

Thread Thread
frontendphil profile image
Philipp Giese Author

Glad I could help. Maybe look at it like this. If it's hard for you to write a test then it will also be hard for people with a screen reader to navigate your app. This motivated me a lot to get more comfortable with accessibility.

What you're describing looks like this to me:

const { getByLabelText, getByRole } = render(<Form />)

fireEvent.change(getByLabelText('Text input'), { 
  target: { 
    value: 'input value' 

fireEvent.focus(getByLabelText('Dropdown'))'option', { 
  name: 'The option you want to select' 
Enter fullscreen mode Exit fullscreen mode

Accessing the request is a tricky thing. However, we can make this more accessible in our tests as well. For instance, we've created a custom render method that fits our codebase. So when we use our custom render we can do more things:

const { getFetchRequest, getByRole } = customRender(<Form />)'button', {
  name: 'Submit'

const request = getFetchRequest(SUBMIT_REGISTRATION)

expect(request.body).toHaveProperty('emailAddress', '')
Enter fullscreen mode Exit fullscreen mode

We've taken what is complicated (getting the request) and turned it into a feature of our custom render wrapper so that developers can write tests more easily.

This can be true for a lot of things. redux, session handling, theming, etc... If something is very common in your app there is no rule that should keep you from extending your testing utilities.