Unit Testing 8 min read

How to Unit Test?

A practical, opinionated guide to writing deterministic, meaningful unit tests for Java classes.

By JAIPilot Team

What is a unit test?

A unit test is “a form of software testing by which isolated source code is tested to validate expected behavior.” In the Java world, this boils down to testing a class in isolation.

From this definition, it makes sense to ensure that no external requests outside of the class are made and that all data is self-contained via mocks. A unit test should solely test the entity in and of itself—the entity being a class in the case of Java.

If the mocks are proper, every unit test becomes deterministic and nothing is flaky anymore. Unit tests should only be used to test class functional logic, not databases and other integrations.

Why unit tests can cover functional behavior

When done right, unit tests are almost all that is required for guaranteeing a high-quality software product. All functional tests can (and should) be covered by unit tests.

The reason for this is based on a simple principle: if a single class A integrates with other classes P, Q, R, and these dependent classes are properly unit tested, then unit testing class A ensures that all AP, AQ, AR integrations are tested as well.

Mathematically, you can think of it like this. Each class has an invariant that its unit tests guarantee:

Given:
  - Class P works correctly (because its unit tests pass)
  - Class Q works correctly (because its unit tests pass)
  - Class R works correctly (because its unit tests pass)

If the unit tests for class A check:
  - A behaves correctly whenever P, Q, and R behave correctly

Then:
  - The combined behavior of A + P + Q + R will be correct for all tested inputs.

In other words, if the invariants of the dependencies hold (because they are unit tested) and the invariant of A holds under those assumptions, then the integrated behavior is correct for all the inputs covered by the unit tests. What this translates to is that unit tests are all that you require for 100% functional correctness for your software.

What unit tests should cover

Unit tests are for testing everything within the code and business logic. That includes: validations, code rules, mocked database calls, and algorithms. Any logical failure can and should be caught by the right unit test.

For every bug found, add a test. For every commit, add or update unit tests. Ensure that the tests are deterministic and run quickly. Clean up unused unit tests. Write both happy-path and failure-path tests.

It is highly recommended to use JAIPilot to write unit tests for you. Keep a build-time test coverage & latency check to ensure that your test executions are aligned with your organization's coverage goals.

When and how to write unit tests

Unit tests can be written after the code is written, by then writing the appropriate unit tests for that code. They can also be written via TDD (Test-Driven Development), where the tests are written first and the actual class logic comes later.

Purists would recommend TDD, but practically both approaches work well depending on the developer's comfort level, organization culture, the stage of life the software product is in, and how mature the product is.

No matter how the tests are written, they must be meaningful, run fast during build time, be deterministic and non-flaky, and cover the usual happy paths, failure paths, and edge cases. Tests should not be written just for the sake of it, but having even half-decent tests— and a lot of them—surely does not hurt.

Summary

Always write tests if you expect your code to last at least for a few months during PMF. Feel free to be quick and dirty if your software product is pre-PMF or early PMF—there is no strict need to write tests then.

Writing unit tests purely to satisfy a mandatory organizational coverage checklist (like >80% unit test coverage) is not always a bad thing. More tests are usually better, and even half-decent unit tests can catch a few errors.

The higher your unit test coverage and quality, the less you will depend on integration tests for functional correctness.

Ready to Generate Better Tests?

Start using JAIPilot to create robust unit tests for your Java projects with AI-powered automation.

Get Started Free