In my on-going quest to master computer programming, I have really tried my best to level up in the past couple of months. I stepped away temporarily from my newly established comfort zones in Front End and Mobile development, and dove deeply into the Back End. I designed a project for myself, and with the guidance of some fabulous mentors at Red Squirrel, I gained competency in Python, creating a production Django app for a retail site I had dreamed up. I also had a solid introduction into SQL and my web app was working lightening fast, but more on that project later.
After getting my web app into a shape that my mentors and I were happy with, it was time to further expand my portfolio and get back into my comfort zone a bit- and I was taking some new best practices I had learned with me- namely TDD, or Test Driven Development. I think that testing in general doesn’t get enough love in Front End development- it feels like it’s an afterthought, or something to be saved for the QA team. It’s also hard- testing state and decoupling UI from logic to test is a different challenge, but that’s not an excuse! I also suspect that we get a bit lazy, because our work is so visual, we get very direct feedback from our code as confirmation during the development process. This might give us a false sense of safety that everything is working fine, but that is false confidence, and we gain better confidence that our code is working as expected if we test. This is the main reason why to write tests, but now I’d like to go further into the Who, What, Why, When, Where, and then maybe some How as well.
- Confidence- As stated above, it gives yourself, your team, and stakeholders confidence that your code is working as expected.
- Starting and getting Unstuck- With TDD in particular, it gives you a place to start. Staring at a blank screen can be intimidating, as Eve Porcello explains in her talk from Women of React Conf 2020. Likewise, if you find yourself stuck, breaking down the parts and writing tests can help uncover a path forward.
- Making API design decisions- It provides guidance for breaking down and isolating concepts, which makes the code easier to understand and easier (and cheaper) to maintain. Testing also provides a framework for examining the code from all angles, allowing for earlier and easier bug catching.
- Refactoring- Tests allow you to refactor with confidence and assurance that the app will still work as expected, along with the same improvement benefits as listed above.
- Level up- If you’re still in junior territory like me, one of the ways to show you’re thinking ahead and preparing to level up is by writing tests. Impress your colleagues and interviewers by adding tests, or if there is a lack of time, explaining why you didn’t, and the benefits of testing. Also, writing tests just make you a better developer. Just think of how practicing writing tests with all the above benefits adds up — to being more confident in your code, getting started and unstuck faster, becoming more efficient at code design and writing code with less bugs… yes, please!
What? The Basics…
First a glossary of some terms and concepts:
Test double- an object that looks and behaves like a production object, but simplifies behavior. Types of test doubles:
1. Fake- objects with working implementations, but not the same as the production objects
2. Stub- canned answers to calls
3. Mock- imitation of an object or function to test behavior, because it can be very impractical to call on real APIs for tests
4. Spy- a real object, but wrapped to inspect how it is called
Shallow rendering- does not render children components
Assert/Assertion- coding an expected result so that it returns a boolean to pass or fail a test
Coverage- a metric to assess how much source is covered. (See more details in this article by Atlassian.)
Parts of Testing
- Set up- (Given)
- Do- (When)
- Verify- (Then)
Example: A basic counter example that increases a displayed number when clicked- Given a button with a count number of 0, When the button is clicked, Then the count number displayed should increase by 1 and show 1.
Test Driven Development (TDD)- a process when tests are written before the production code. The steps are:
- Write a test
- Run the test (red)
- Write code to pass the test
- Run the test (go green)
- Run the test (go green)
I would strongly suggest following a TDD approach for of all the benefits I have already outlined above are very pronounced when one starts the process with testing.
However, it’s never too late to add tests and gain confidence that your production code is working as expected. And in full disclosure, as I am still getting used to TDD myself, sometimes I find myself starting with a basic test, writing production code, adding additional logic and functionality and then realizing that I need to go back and write additional tests. And in that way, I am still gaining benefits and writing better code, but I think if I get used to writing the tests first, I will save myself a bit of debugging and refactoring later.
To each their own! I think we all have room to discover what works best for our work flows.
Which leads nicely into…
I would suggest everyone, if it even needs to be stated, but I like to cover all bases. Re-read the arguments about why testing… if you are contributing to the code, you can and should contribute to code confidence.
What? The specifics…
This was one of the hardest questions for me to answer as I was getting into writing tests for the Front End. It seemed so much easier on the Back End… So, let’s break it down into a few questions first.
What does NOT need to be tested? I like to start here because quite often it can be easier to start with the negative/false cases.
- Everything- Testing everything is not realistic nor desirable. By writing tests, time is being taken away from something else- something that might have more value. So let’s just state that outright and continue in that vein of thought.
- 3rd party libraries- They should already be tested by their own maintainers. These modules are often candidates for needing to be test doubled- faked or mocked in your tests so that the focus is on your logic and production code.
- User Interface (UI)- It is often not worth the valuable time to test changes to UI, unless there is a visual updated based on interaction or behavior. Simple design layouts are often changing and thinking about testing each time an update is made and then changing the expected results doesn’t sound like a valuable use of time, right? However, ensuring that a button click results in the correct information showing? That sounds more valuable.
Now I think we are in a good place to ask: What needs to be tested?
Most of my work in Front End is based upon React and React Native- which means components. So, for my work, components need to be tested. But let’s break this down further. A quote from the React doc:
In understanding this, components can be broken down and separated into two categories.
- functions and knowledge of logic — context, side effects, domains (app state and behavior)
- doing something with that knowledge by showing view interfaces — presentational (UI)
Now that we’ve thought about this conceptually, it’s much easier now to know what to test. As outlined above, the UI doesn’t need to be tested. That leaves components (or parts of the components) that are functions, context, side effects and state (which also means hooks, if using functional components). Nice.
Some additional questions to ask as this point are- What are all of the side effects and state options and are they being tested? Can this break? How? If it does break, is the desired outcome being tested? Tests should be written to account for all special outcomes. For a more detailed explanation, far better than I can go on this at this time, watch this talk given by Jim Weirich ❤.
Now, What value do these tests add? This is one last question that is good to add, which actually has a few sub-questions to really understand the answer. But it is good to ask, like in the above explanation of why we don’t need to test UI to reinforce why we are writing tests in the first place. If the tests aren’t valuable or are constantly changing so that we don’t have faith in them, then we are misdirecting our valuable time and effort when we could be working on something more important. To check the value add, I also ask: if the production code is changed, will the tests still pass? Is this testing the functionality of the app?
Now I have asked and answered the 5 W’s, excluding the where, because, well, as with most programming, you can do it anywhere and you should. So now, for the even better stuff…
How!? The basics…
As with all things in life, there are many layers. In this article, like most of those out there, I will only touch on the high level layer of how. But if this article has been enjoyable, fret not, I’m already working on an even deeper dive into the testing of how, with my open repository. And I will also provide links to other resources I’m also using to level up.
There are 4 different ways to test:
- End-to-end testing (e2e)- Is the most time intensive and involved testing. These types of tests automate and validate user journeys through the app. This type of testing requires specialized libraries- Detox for React Native and Cypress for React are two commonly used frameworks.
- Integration testing- Testing how the pieces together behave.
- Unit testing- Each item does what it is supposed to do in isolation.
- Snapshot testing- A recording of the DOM tree (UI) of the tested structure that is preserved (like a snapshot in time) to compare changes the next time the tests are run. These are the most easy, general, generic, quick and dirty tests to write.
These testing types are on a scale from most expensive and time consuming to least. Developers have differing opinions on what combination of the above is the best strategy, however, all would agree that some mixture of all the above will offer adequate coverage. Understanding the pro’s and con’s of each will help determine strategy.
I will leave it up to you, dear reader, to take your pick of a framework and do some exploration of the syntax and documentation to get more comfortable with the basics of the “how”.
I would also recommend that in addition to the resources already sprinkled throughout this article to take a look at the following resources:
- 99 Bottles by Sandi Mentz- Sandi focuses on object oriented programming through TDD and gives a superb explanation of how the TDD drives the development and refactoring to beautifully elegant, understandable, and maintainable code.
- Therapeutic Refactoring by Katrina Owen- A great talk on refactoring through testing. Just watching her presentation is therapeutic!
- Effective React Testing Talk by Jeremy Fairbank- Still unsure of how or where to start? Jeremy gives a good overview of everything discussed above and then live codes some TDD of a simple app.
- React MVC by Tommy Groshong- I was already hinting in the above “What? The specifics…” section at modularity in components to separate concerns by isolating components based on logic vs presentation. This article by Tommy dives deeper into the value add of an MVC model for React components that I will be following in my follow up companion piece in “Testing: How!? The specifics…”
Now you have a little bit more background on what testing is, why and when to do it and a few options on how to get started. Stay tuned for more how.
And Happy testing!