DEV Community

Cover image for Unit testing
Amandeep Singh
Amandeep Singh

Posted on

Unit testing

Testing at the unit level reduces the risk of unexpected behaviors, giving developers confidence that their code performs reliably. In this series, we’ll cover unit testing with:

  • JavaScript/Typescript (React) for Web UI Components
  • Python for API Endpoints
  • Go for Backend Logic

We’ll dive into strategies, role in SDLC, specific examples and best practices, covering essential tools and techniques that will help you test more effectively.

Unit testing strategies

To create unit tests, you can follow some basic techniques to ensure coverage of all test cases.

  1. Logic checks - Does the system perform the right calculations and follow the right path through the code given a correct, expected input? Are all paths through the code covered by the given inputs?

  2. Boundary checks - For the given inputs, how does the system respond? How does it respond to typical inputs, edge cases, or invalid inputs?

  3. Error handling - When there are errors in inputs, how does the system respond? Is the user prompted for another input? Does the software crash?

  4. Object-oriented checks - If the state of any persistent objects is changed by running the code, is the object updated correctly?

Unit Testing and SDLC

Unit testing plays a critical role throughout the Software Development Lifecycle (SDLC), helping developers detect and fix issues early on, reducing costly fixes later in the process. Unit testing can be integrated into SDLC in a few main approaches:

  1. Test-Driven Development (TDD): In TDD, tests are written before the actual code. Developers begin by writing a failing unit test for a small piece of functionality, then implement the code to pass the test, and finally refactor the code if needed.

  2. Testing After Completing a Code Block: Often, developers write tests after completing a block of code, verifying its behavior against expected outcomes.

  3. Automated Testing in CI/CD Pipelines: Once tests are written, integrating them into CI/CD pipelines ensures that they run automatically whenever new code is added catching potential issues before they make it to production.

Best practices

Best practices

  1. Mock Dependencies

    • Isolate the functionality under test by mocking external dependencies. This allows you to test components independently and assume that external modules or APIs will behave as expected.
  2. Naming

    • The name of unit test should describe the intent of the test to us clearly. We should be able to infer method behavior from the name of the method without looking at the code itself.
  3. Organize your tests into three clear steps, following the AAA (Arrange, Act, Assert) pattern:

    • Arrange (Setup) - Prepare the necessary environment, mock data, and dependencies required for the test.
    • Act (Stimulus) - Execute the function or method that you want to test.
    • Assert (Expectation) - Verify the result by asserting that the outcome matches the expected behavior.
  4. Avoid Snapshot Testing for Units

    • Snapshot testing can obscure specific failures. Instead, write tests with explicit assertions that clearly outline expected behaviors.
  5. Use Visual Testing for Complex UI Components

    • For testing visual elements like charts, maps, and graphs, consider visual testing. Capture and compare screenshots to detect unintended changes over time.
  6. Use Simple, Readable Data Sets for Assertions

    • Choose datasets that are straightforward and easy to understand, minimizing cognitive load for anyone reviewing the tests.
  7. Minimize Flakiness

    • Design tests to be consistent and reliable by avoiding dependencies on fluctuating states or unpredictable data.
  8. Use Accessibility Attributes as Selectors

    • When possible, use accessibility attributes to select elements for testing. This can improve both test accuracy and app accessibility.
  9. Avoid Global State

    • Keep tests independent by avoiding global state that can cause unexpected interactions between tests. Each test should be isolated in its execution.
  10. Use Dependency Injection

    • Use dependency injection to pass external dependencies into your unit, allowing for better control and flexibility during testing.
  11. Aim for High Code Coverage

    • Strive for at least 85% line coverage to ensure a thorough evaluation of your code and increase confidence in its reliability.

Top comments (0)