First Steps in Frontend Testing with TDD/BDD

Ariel Herman
6 min readApr 9, 2018

--

As I look forward to landing my first job as a developer, I’ve noticed one critical skill keeps popping up in interviews: “TDD” or Test-Driven Development. At Flatiron we worked through seemingly infinite labs designed around getting tests to pass to self-evaluate our knowledge of a concept but I never had the opportunity to do anything other than manual testing within my application. To better prepare myself for the real world of production code, I decided to research frontend testing more thoroughly and try to write some tests of my own.

Why Test?

Testing is code that checks that the code that you write in your app, also called “production code,” works as expected. It may seem like littering your application with debuggers and console.logs to monitor the behavior of your application is sufficient, but this really doesn’t scale. For one thing, although this type of testing gives you information on whether your code is working, it doesn’t give you much detail about why it breaks. All of this manual testing code must be commented out, commented on, or removed later, which is not only a lot of extra work for the dev but adds a lot of clutter to the code base that doesn’t pertain to the actual function of the application.

Automated testing, on the other hand, gives you informative and semantic results. The test files are separated from the real code of your application. In addition, it guarantees the code works as you expect it to. When you have a set of unit tests verifying your code works, you can safely change the code and trust that other parts of your program will not break, eliminating bugs in the long run. Plus, passing tests is satisfying and addictive. Seeing that little green check just makes you want to keep coding!

The Testing Spectrum

TDD is so named because of the theory that the tests should drive the development, eg. you should write the test first before writing any code. It can be just as effective to write tests alongside your production code but TDD is popular because, as with many things in life, a little planning can go a long way to avoid problems down the line. Kicking off the development process with tests encourages you to think ahead of time about the functionality your app should have. Because of that, it can also prompt you to have more thorough discussions with stakeholders to make sure what you’re writing is actually what they’re looking for.

There are a various testing methods but most of them fall into these three camps sorted from most fine-grain to large-scale:

  • Unit tests validate the functionality of isolated code. They test the output of a “unit,” a pure function that always gives the same result for a given input. The bulk of the tests written for an application will be unit tests and these tests should be written in isolation from dependencies (network, database, or other code). As we will see, unit tests and functional programming make a good match.
  • Integration tests verify the flow of data and the interaction of components. Once you want to examine the combined behavior of two or more units together, it is an integration test.
  • End-to-End (E2E) tests look at the behavior of the overall application. Very few of these should be written for the application because if unit tests and integration tests are passing you shouldn’t need much E2E testing. They’re also expensive and time-consuming.

Whichever test you’re writing, the TDD flow is as follows (from Codetopia):

  1. Write a test (see this blog post for more tips on writing a great test)
  2. Run the test and any other tests. At this point, your newly added test should fail. If it doesn’t fail here, it might not be testing the right thing and thus has a bug in it.
  3. Write the minimum amount of code required to make the test pass
  4. Run the tests to check the new test passes
  5. Optionally refactor your code
  6. Repeat from Step 1 for the next part of your application

Challenges of Frontend Testing

Many devs see frontend testing as more difficult and less rewarding than backend testing. After all, it’s much easier to predict how code will work or an API will be consumed than the myriad of ways that a user can interact with the UI. Testing also becomes difficult to do when you’re not unit testing (hint: you should be) or testing something that has a lot of dependencies.

As this article from Cloudboost puts it: “Not everything running in the browser is hard to test or even frontend code. You should be trying to extract as much business logic from your UI and put it into pure functions, tested via TDD as possible.”

A companion to TDD is Behavior-Driven Development or “BDD.” Working with a BDD mindset can be extremely beneficial for frontend testing. A poorly-written test will check for implementation rather than behavior. If the implementation changes as your application evolves or is refactored, then the test must change as well even though the behavior is identical. This nullifies the time-saving benefits of TDD.

Codetopia has a good example of coding for behavior rather than implementation: suppose we want to test a simple counter object. A unit test that checks that calling the function tick() increases the count to 1 is checking for implementation rather than behavior because it assumes the counter has started at 0, which might not be true for all applications of the counter. Using BDD, the test should instead verify that calling tick() increases the counter by 1, which makes sense for all uses of the counter.

Benefits of Functional Programming

Functional programming is programming that relies on pure functions. Pure functions don’t rely on external state or variables, don’t cause side effects or alter external variables, and always return the same result for the same input. I like to think of them as programming’s answer to a scientific control. As such, they are ideal for unit testing and this article has a very good takedown of the benefits. In short, since every input of functional code has a consistent output, it can be tested in isolation and if it works you can be sure other parts of your program won’t break. A good gut check is if you have you think hard about how to write your unit test you’re probably not writing it correctly.

Testing With Javascript and React

Some of the most popular Javascript testing tools are Mocha.js, Chai, Jasmine, and Jest. There is also Enzyme, a newer testing tool built by AirBnB for testing React Components. Since I’m a newbie, I wanted to begin with Chai, a common expectation library. A test suite written with Chai contains a series of assertions (using the expect() function) and throws an error when any of those expectations is not met.

Install mocha chai to begin: yarn add mocha chai --dev

(The dev flag was new to me. It just means that you don’t need it as a dependency for your production code, only for the development environment.)

Writing good tests means working in a “red-green-refactor” pattern with BDD in mind. In the code below testing a function that takes in a string and returns it title cased, you can see I have written a series of tests. At each point I will run “node textUtilities.js” to see the errors returned from my .expect() calls. Then I go back and write just enough within my original function to get that specific error to disappear. That, in a nutshell, is the basics of TDD.

That’s Not All

The next (and crucial) step is to separate out the expectations from the running code, but this was just a starter to practice the principles of TDD/BDD! Check out the follow-up post I wrote with steps on how to organize your test and production files and run tests with npm.

Sources and Resources:

Testing React Apps (Jest Documentation)
Test Utilities (React Documentation)
Testing Your Frontend Code Part I
A Beginner’s Guide to Testing Functional Javascript
Why TDDing Your Frontend Feels Pointless
Should You Unit Test React Components?
Unit Testing With TDD and BDD
Team Treehouse: Javascript Unit Testing (Video)
Unit Testing Best Practices With AngularJS
What Every Unit Test Needs
A Guide to Unit Testing In Javascript
Unit Testing Best And Worst Practices

--

--