LearnThatStack Ace your next interview
Testing · Free

JUnit.
Interview cheat sheet.

Quick reference for JUnit - sectioned for fast scanning. Skim the part you're shaky on, walk in confident.

Testing 20-section reference ~7 min read

Summary

JUnit is Java's premier unit testing framework, powering test-driven development with annotations, assertions, and lifecycle management. Master JUnit 5's architecture, parameterized tests, nested tests, conditional execution, and integration with mocking frameworks to write maintainable, efficient test suites that ensure code quality.

1. JUnit Basics

What is JUnit?

  • Unit testing framework for Java
  • Current versions: JUnit 5 (Jupiter) is latest, JUnit 4 still widely used
  • Part of xUnit family of testing frameworks

JUnit 5 Architecture

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
         (Foundation)      (New tests)     (JUnit 3/4 support)

2. Essential Annotations

JUnit 5 Annotations

@Test                  // Marks a test method
@BeforeEach           // Runs before each test (setUp)
@AfterEach            // Runs after each test (tearDown)
@BeforeAll            // Runs once before all tests (static)
@AfterAll             // Runs once after all tests (static)
@DisplayName("name")  // Custom test name
@Disabled             // Skip test
@TestInstance         // Control test instance lifecycle

JUnit 4 → JUnit 5 Migration

@Before      → @BeforeEach
@After       → @AfterEach
@BeforeClass → @BeforeAll
@AfterClass  → @AfterAll
@Ignore      → @Disabled

3. Basic Test Structure

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    private Calculator calc;
    
    @BeforeEach
    void setUp() {
        calc = new Calculator();
    }
    
    @Test
    @DisplayName("Should add two numbers correctly")
    void testAddition() {
        // Given
        int a = 5, b = 3;
        
        // When
        int result = calc.add(a, b);
        
        // Then
        assertEquals(8, result, "5 + 3 should equal 8");
    }
    
    @AfterEach
    void tearDown() {
        calc = null;
    }
}

4. Assertions

Common Assertions

// Basic assertions
assertEquals(expected, actual);
assertNotEquals(expected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(object);
assertNotNull(object);

// With custom message
assertEquals(5, result, "Custom failure message");

// Array assertions
assertArrayEquals(expectedArray, actualArray);

// Object reference
assertSame(expected, actual);      // Same reference
assertNotSame(expected, actual);

// Grouped assertions
assertAll("person",
    () -> assertEquals("John", person.getFirstName()),
    () -> assertEquals("Doe", person.getLastName())
);

Exception Testing

// JUnit 5
@Test
void shouldThrowException() {
    Exception exception = assertThrows(
        IllegalArgumentException.class, 
        () -> calculator.divide(10, 0)
    );
    assertEquals("Cannot divide by zero", exception.getMessage());
}

// JUnit 4
@Test(expected = IllegalArgumentException.class)
public void shouldThrowException() {
    calculator.divide(10, 0);
}

Timeout Testing

// JUnit 5
@Test
@Timeout(value = 2, unit = TimeUnit.SECONDS)
void timeoutTest() {
    // Test fails if execution exceeds 2 seconds
}

// Or with assertTimeout
@Test
void timeoutNotExceeded() {
    assertTimeout(Duration.ofSeconds(2), () -> {
        // Code that should complete within 2 seconds
    });
}

5. Parameterized Tests

@ParameterizedTest with Different Sources

// Value Source
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void testWithValueSource(int number) {
    assertTrue(number > 0 && number < 6);
}

// CSV Source
@ParameterizedTest
@CsvSource({
    "1, 1, 2",
    "2, 3, 5",
    "3, 5, 8"
})
void testAddition(int a, int b, int expected) {
    assertEquals(expected, calculator.add(a, b));
}

// Method Source
@ParameterizedTest
@MethodSource("provideTestData")
void testWithMethodSource(String input, boolean expected) {
    assertEquals(expected, validator.isValid(input));
}

private static Stream<Arguments> provideTestData() {
    return Stream.of(
        Arguments.of("test@email.com", true),
        Arguments.of("invalid.email", false)
    );
}

// Enum Source
@ParameterizedTest
@EnumSource(Month.class)
void testAllMonths(Month month) {
    assertNotNull(month);
}

6. Test Lifecycle & Instance

Test Instance Lifecycle

// Default: new instance per test method
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
class DefaultLifecycleTest { }

// Single instance for all tests
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SingleInstanceTest {
    // @BeforeAll and @AfterAll don't need to be static
}

Test Execution Order

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class OrderedTest {
    @Test
    @Order(1)
    void firstTest() { }
    
    @Test
    @Order(2)
    void secondTest() { }
}

7. Nested Tests

@DisplayName("Stack Test")
class StackTest {
    Stack<Integer> stack;
    
    @Test
    void isInstantiatedWithNew() {
        new Stack<>();
    }
    
    @Nested
    @DisplayName("when new")
    class WhenNew {
        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }
        
        @Test
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }
        
        @Nested
        @DisplayName("after pushing element")
        class AfterPushing {
            @BeforeEach
            void pushElement() {
                stack.push(42);
            }
            
            @Test
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }
        }
    }
}

8. Conditional Tests

// OS-specific tests
@Test
@EnabledOnOs(OS.LINUX)
void onlyOnLinux() { }

@Test
@DisabledOnOs({OS.WINDOWS, OS.MAC})
void notOnWindowsOrMac() { }

// JRE version
@Test
@EnabledOnJre(JRE.JAVA_11)
void onlyOnJava11() { }

@Test
@EnabledForJreRange(min = JRE.JAVA_8, max = JRE.JAVA_11)
void fromJava8To11() { }

// System property
@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
void onlyOn64BitArchitecture() { }

// Environment variable
@Test
@EnabledIfEnvironmentVariable(named = "ENV", matches = "prod")
void onlyInProduction() { }

9. Assumptions

@Test
void testOnlyOnCiServer() {
    // Test only continues if assumption is true
    assumeTrue("CI".equals(System.getenv("ENV")));
    
    // Rest of test
    assertEquals(2, calculator.add(1, 1));
}

@Test
void testWithAssumption() {
    assumingThat("DEV".equals(System.getenv("ENV")),
        () -> {
            // This code only runs if assumption is true
            assertEquals(2, calculator.add(1, 1));
        });
    
    // This always runs
    assertNotNull(calculator);
}

10. Test Doubles (Mocking)

Using Mockito with JUnit

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    void testGetUser() {
        // Given
        User mockUser = new User("John", "Doe");
        when(userRepository.findById(1L)).thenReturn(mockUser);
        
        // When
        User result = userService.getUser(1L);
        
        // Then
        assertEquals("John", result.getFirstName());
        verify(userRepository).findById(1L);
    }
}

11. Test Fixtures & Resources

class FileTest {
    @TempDir
    Path tempDir;  // JUnit creates temp directory
    
    @Test
    void testFileCreation() throws IOException {
        Path file = tempDir.resolve("test.txt");
        Files.writeString(file, "Hello Test");
        
        assertTrue(Files.exists(file));
        assertEquals("Hello Test", Files.readString(file));
    }
}

12. Best Practices

Test Naming Conventions

// Method name describes what is being tested
@Test
void shouldReturnTrueWhenInputIsValid() { }

// Given-When-Then pattern
@Test
void givenNullInput_whenValidate_thenThrowException() { }

// Display name for readability
@Test
@DisplayName("Null input should throw IllegalArgumentException")
void testNullInput() { }

Test Structure - AAA Pattern

@Test
void testCalculation() {
    // Arrange (Given)
    Calculator calc = new Calculator();
    int a = 5, b = 3;
    
    // Act (When)
    int result = calc.add(a, b);
    
    // Assert (Then)
    assertEquals(8, result);
}

F.I.R.S.T Principles

  • Fast: Tests should run quickly
  • Independent: Tests shouldn't depend on each other
  • Repeatable: Same result every time
  • Self-validating: Pass or fail, no manual inspection
  • Timely: Written just before production code (TDD)

13. Key Interview Topics

Lifecycle Annotations Comparison

  • @BeforeEach: Runs before each test method, creates fresh state
  • @BeforeAll: Runs once before all tests, must be static (unless PER_CLASS)
  • @AfterEach: Cleanup after each test, prevents test pollution
  • @AfterAll: Final cleanup, releases shared resources

Exception Testing Strategies

// JUnit 5 - Preferred approach
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
    calculator.divide(10, 0);
});
assertEquals("Cannot divide by zero", exception.getMessage());

// Test doesn't throw exception
assertDoesNotThrow(() -> calculator.divide(10, 2));

Test Management Techniques

// Disable with reason
@Disabled("Feature under development")
@Test
void futureFeatureTest() { }

// Conditional execution
@EnabledOnOs(OS.LINUX)
@DisabledIfEnvironmentVariable(named = "CI", matches = "true")

Performance Testing Approaches

// Timeout annotation
@Test
@Timeout(value = 2, unit = TimeUnit.SECONDS)
void shouldCompleteQuickly() { }

// Assertion with timeout
assertTimeout(Duration.ofMillis(100), () -> {
    // Code that should complete within 100ms
});

Test-Driven Development (TDD) Cycle

  • Red Phase: Write failing test that defines desired behavior
  • Green Phase: Implement minimal code to pass test
  • Refactor Phase: Improve code while keeping tests green
  • Benefits: Better design, documentation, regression safety

14. Advanced Features

Dynamic Tests

@TestFactory
Stream<DynamicTest> dynamicTests() {
    return Stream.of("A", "B", "C")
        .map(str -> DynamicTest.dynamicTest(
            "Test for " + str,
            () -> assertNotNull(str)
        ));
}

Test Templates

@TestTemplate
@ExtendWith(MyTestTemplateProvider.class)
void testTemplate(String parameter) {
    assertNotNull(parameter);
}

Custom Annotations

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Test
@Tag("integration")
@Timeout(5)
public @interface IntegrationTest { }

// Usage
@IntegrationTest
void myIntegrationTest() { }

15. Testing Patterns

Test Data Builders

class UserBuilder {
    private String name = "Default";
    private int age = 25;
    
    public UserBuilder withName(String name) {
        this.name = name;
        return this;
    }
    
    public UserBuilder withAge(int age) {
        this.age = age;
        return this;
    }
    
    public User build() {
        return new User(name, age);
    }
}

// Usage in test
User testUser = new UserBuilder()
    .withName("John")
    .withAge(30)
    .build();

16. Integration with Build Tools

Maven Configuration

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.0</version>
    <scope>test</scope>
</dependency>

Gradle Configuration

testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'

test {
    useJUnitPlatform()
}

17. Quick Reference - Assertion Methods

Assertion Description
assertEquals(expected, actual) Check equality
assertTrue(condition) Check if true
assertFalse(condition) Check if false
assertNull(object) Check if null
assertNotNull(object) Check if not null
assertThrows(exception, executable) Check exception thrown
assertTimeout(duration, executable) Check execution time
assertAll(executables) Group assertions
fail(message) Fail test explicitly

18. Common Pitfalls to Avoid

  1. Don't use static variables - Tests should be independent
  2. Don't test private methods directly - Test through public API
  3. Don't write overly complex tests - Keep them simple and focused
  4. Don't ignore failing tests - Fix or remove them
  5. Don't test implementation details - Test behavior, not implementation
  6. Don't skip assertions - Every test needs assertions
  7. Don't use production resources - Use test doubles/mocks

19. Interview Success Strategies

Technical Proficiency

  • Version Differences: JUnit 4 vs JUnit 5 migration paths
  • Lifecycle Management: Test instance lifecycle, method ordering
  • Mocking Integration: Mockito, test doubles, dependency injection
  • Assertion Mastery: Choose appropriate assertions for scenarios
  • Test Organization: Nested classes, parameterized tests, test factories

Best Practices to Demonstrate

  • Test Isolation: Each test runs independently
  • Clear Naming: Descriptive test and method names
  • AAA Pattern: Arrange-Act-Assert structure
  • Edge Cases: Null, boundary, exceptional scenarios
  • Performance: Fast execution, avoid Thread.sleep()

Communication Points

  • TDD Benefits: Design improvement, living documentation
  • Test Strategy: Unit vs integration vs E2E decisions
  • Coverage Metrics: Line, branch, mutation testing
  • Maintenance: Refactoring tests with production code

Code Quality Indicators

  • FIRST Principles: Fast, Independent, Repeatable, Self-validating, Timely
  • Single Responsibility: One assertion concept per test
  • DRY: Avoid duplication with @BeforeEach, test utilities
  • Readability: Tests as documentation
Found this useful? Pass it on.
Pro · $10/mo

The sheet is free. Pro goes deeper.

Pro opens the full question library behind every sheet, every refresher and a monthly AI allowance. One subscription, all formats.

Full question library All refreshers Cancel anytime