DEV Community

fajarriv
fajarriv

Posted on

Test-Driven Development (TDD) in frontend code.

tdd image
Image taken from here.

What is Test-Driven Development(TDD)?

Test-Driven Development (TDD) is a software development approach where developers write tests before writing the actual code. TDD follows a simple and iterative cycle, often referred to as the “red-green-refactor” cycle:

red-green-refactor image example

  1. Write a failing test (red): Start by creating a test that describes the desired behavior of a piece of functionality. This test initially fails because the corresponding code doesn’t exist yet.
  2. Write the minimum code (green): Write the minimum amount of code necessary to make the test pass. This code might be incomplete or inefficient at this stage.
  3. Refactor: Once the test passes, refactor the code to improve its quality while ensuring that the tests continue to pass.

TDD: Frontend vs Backend

TDD excels in backend development because the world of backend logic is more predictable. Backend code typically deals with data manipulation, business rules, and API interactions. These tend to be well-defined and less prone to change compared to frontend UI design. TDD's upfront test writing style works well in this stable environment.

Frontend development, on the other hand, is a more fluid world. UI designs can evolve throughout the development process based on user feedback or changing requirements. Tests written for an initial design might need adjustments as things change. Additionally, TDD can make developers focus on functionality over visual design initially. While crucial for a solid foundation, this might feel restrictive for UI-focused developers who want to explore design options freely.

Unit testing

Unit testing is a software development technique where individual units of source code, typically functions, classes, or components, are tested to verify their correctness in isolation. Unit tests are designed to be independent and self-contained, meaning they should not rely on external factors or interact with databases or other external systems.

Writing test cases

Writing effective test cases is crucial for ensuring the quality and reliability of your frontend application. Well-crafted tests help catch bugs early in the development process, prevent regressions, and boost developer confidence when refactoring code.

There are various techniques we can use to write comprehensive test cases such as user story based testing, happy path vs sad path testing, and component behavior-based testing.

Journey of writing unit test

Note: Examples for frontend are written using jest with react-testing-library.

I never tested my frontend code before; I only used to write backend tests. For me, writing a backend unit test is always much easier than writing a frontend unit test because of the predictable logic on the backend.

Here's example of my backend unit test. Writing these tests very straightforward, first we setup the data/state for our input and processing. After that we write test for positive, negative, and edge cases.
setup for testing
testcases
test result


My journey of writing unit test in frontend began with reusable components, the building blocks of UIs. Here, the focus was on ensuring the component itself functioned as intended in isolation. So, the main case that we want to test is the correctness of the rendered component.
statuschip test
test-result-statuschip


Next, I tackled testing hooks for fetching data, the functions responsible for retrieving data from external sources. Testing these hooks became crucial to ensure our application displayed accurate data. Here, unit tests allowed me to mock the API responses, simulating different scenarios like successful data retrieval or errors.

daftarPilihanJadwal
testresultHooks


Finally, I need to write test for large component/page component. Writing test for this type of components will be tricky. It's because this kind of component depends to other components or hooks. In order to isolate the SUT we need to mock other components/hooks that used inside the SUT.

When writing the test cases, it's easier for me to think based on behaviors of individual UI components rather than thinking of successful and unsuccessful scenarios of components.

For example, i have a button component that will open a modal/dialog that ask confirmation to delete an entity.

The first test case comes up in my mind is to test if my button is rendered properly or not. After that i want to make sure the modal is popped up and rendered correctly too. I repeat this process of thinking when writing the modal behavior test cases.
test scenario

Code implementation and test results
code-example
test-result

Things to consider when writing unit tests

There are important considerations to keep in mind when writing effective tests specifically for frontend development such as things that should not be unit-tested.

Target Individual Components: Unit tests should be focused on a single unit of code. This promotes isolation and makes tests easier to understand, maintain, and debug.

Mock External Dependencies: Frontend components often rely on external dependencies like APIs and other components. When unit testing, it's essential to mock these dependencies to prevent them from interfering with your test logic (mock everything outside the Subject Under Test).
mocking example

Follow the Triple A (Arrange, Act, Assert) Pattern: This is a fundamental best practice for structuring unit tests. It promotes clear separation of concerns and makes tests easier to read and maintain.

triple-A example

Benefit from unit testing

One of the most significant advantages is the safety net it provides for refactoring. Refactoring involves restructuring existing code without changing its overall functionality. This can be essential for improving code readability, reducing complexity, and aligning your codebase with best practices. Unit tests act as a guardrail during this process. By running the test suite after making any code changes, we can ensure that the refactored code still produces the expected results. This catches any unintended regressions early on, preventing bugs from creeping in and saving us debugging headaches down the line.

Unit tests essentially give us the confidence to make changes to your codebase knowing that we won't break existing functionality in the process. This empowers us to experiment with different code structures and optimizations, ultimately leading to a cleaner, more maintainable frontend application.

Top comments (0)