Expert · JDBC, Maven, Gradle and JUnit · topic 50 of 60 · 30 min
Unit testing with JUnit
Explanation
Why unit tests?
A unit test checks one small piece of code (a method or class) automatically. A good suite of hundreds of tests runs in seconds and tells you immediately when a change breaks something, so you can refactor and add features without fear. Most Java teams require tests for every change, and interviewers often ask you to write one.
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class GstCalculatorTest {
private GstCalculator calc;
@BeforeEach
void setUp() {
calc = new GstCalculator(18); // a fresh object for every test
}
@Test
void addsEighteenPercent() {
assertEquals(1180.0, calc.withGst(1000), 0.001); // expected, actual, tolerance
}
@Test
@DisplayName("negative amounts are rejected")
void rejectsNegative() {
IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> calc.withGst(-5));
assertTrue(e.getMessage().contains("negative"));
}
}JUnit 5 (the "Jupiter" API in org.junit.jupiter.api) is the common version today; JUnit 6 continues the same Jupiter API. Test classes and methods do not need to be public. Each test method runs on a NEW instance of the test class.
Arrange, act, assert
Structure each test in three parts: arrange (set up the inputs), act (call the method), assert (check the result). One behaviour per test, with a name that reads like a sentence: rejectsNegative, appliesFreeDeliveryAt499. When it fails, the name says what broke.
Parameterized tests
@ParameterizedTest
@CsvSource({
"1000, 1180.0",
"0, 0.0",
"99.5, 117.41"
})
void gstTable(double amount, double expected) {
assertEquals(expected, calc.withGst(amount), 0.01);
}
@ParameterizedTest
@ValueSource(strings = {"", " ", "12345", "ABCDE12345"})
void invalidPans(String pan) {
assertFalse(PanValidator.isValid(pan));
}Mocking with Mockito
A class that sends SMS, calls a payment gateway or reads a database is hard to unit test: slow, costly, unpredictable. Mockito creates mock objects that stand in for those dependencies, return answers you choose, and record how they were called.
import static org.mockito.Mockito.*;
@Test
void sendsOtpOnce() {
SmsSender sms = mock(SmsSender.class); // a fake SmsSender
OtpService service = new OtpService(sms); // inject it
service.sendOtp("9876543210", "482913");
verify(sms, times(1)).send("9876543210", "Your OTP is 482913");
}
@Test
void declinedPaymentDoesNotEmail() {
PaymentGateway gateway = mock(PaymentGateway.class);
Mailer mailer = mock(Mailer.class);
when(gateway.charge(anyLong())).thenReturn(false); // stub the answer
new Checkout(gateway, mailer).pay(49_900);
verifyNoInteractions(mailer);
}Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Unit testing with JUnit · 1/6
In this lesson you will write automated tests with JUnit.
Prefer a video by a teacher? Find free videos on “junit 5 tutorial assertThrows parameterized test mockito” on YouTube ↗
In real life: Testing EMI calculations before a rate change
A lending company's EMI calculator has 60 parameterised test cases covering different principals, rates, tenures and the zero-rate edge case. When the team refactored the formula for performance, the suite caught a rounding change in two cases before it reached customers.
@ParameterizedTest
@CsvSource({ "500000, 9, 60, 10379.18", "100000, 0, 10, 10000.00" })
void emiTable(double principal, double rate, int months, double expected) {
assertEquals(expected, Emi.emi(principal, rate, months), 0.01);
}Practice (0/3 done)
Which assertion checks that a method throws IllegalArgumentException?
Why use @BeforeEach to create the object under test?
For a class BankAccount (deposit, withdraw that throws IllegalStateException on insufficient balance), write a JUnit 5 test class with at least 5 tests: a normal deposit, a normal withdrawal, withdrawing more than the balance (assertThrows), a parameterised test of several deposits, and a test that the balance is unchanged after a failed withdrawal. Run them with mvn test.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- SQL injection and safe queriesJDBC, Maven, Gradle and JUnit · same module
- Unit testing: checking one piece at a timeBackend Development · Testing and API documentation
- Unit tests with JUnit and coroutinesKotlin and Android App Development · Testing Android apps
- Testing basics: unit, integration and end-to-endFrontend Web Development · Fast, accessible, findable and safe: quality for real sites
- Unit testing with xUnitC# and .NET · Testing, tooling and Unity
- Test doubles: fakes, stubs and mocksC# and .NET · Testing, tooling and Unity
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which package holds the JUnit 5 (Jupiter) annotations?
Answer: org.junit.jupiter.api
org.junit was JUnit 4; junit.framework was JUnit 3.
What does Mockito's when(x.call()).thenReturn(5) do?
Answer: Makes the mock return 5 for that call
That is stubbing.
Which command runs the tests in a Maven project?
Answer: mvn test
The test phase runs the Surefire plugin.
What makes a test "flaky"?
Answer: It sometimes fails without any code change
Usually time, randomness, network or shared state.
In Arrange, Act, Assert, what happens in the Act step?Related
Answer: You call the code being tested
Arrange prepares, Act calls the function once, Assert checks the result.
Which annotation marks a JUnit test function?Related
What does Arrange-Act-Assert mean?Related
Answer: Set up the data, call the code, check the result
It is the simple shape of almost every test.
What does Arrange-Act-Assert describe?Related
Answer: The structure of a test: set up, call, check
It keeps tests readable.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “JDBC, Maven, Gradle and JUnit”. The module exam comes next.
- Module 10 exam: JDBC, Maven, Gradle and JUnit · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Spring Boot basics. 60 topics left in the course before the final exam.