Tag Archives: Database

Modern Java Testing: JUnit 6 + AssertJ + Mockito 5 + Testcontainers

JUnit 6 is the first major JUnit release built for Java 17+ and the virtual-thread era. But JUnit alone is only the runner — modern Java test suites pair it with AssertJ for fluent assertions, Mockito 5 for mocking, and Testcontainers for real infrastructure in integration tests. This post ties all four together into a complete, runnable example you can drop into any Spring Boot or plain-Java project.

Continue reading Modern Java Testing: JUnit 6 + AssertJ + Mockito 5 + Testcontainers

Database Testing in JUnit 6: H2 vs Real DB vs Containers

Every Java application talks to a database. Choosing the right testing strategy for that interaction determines how much confidence your test suite actually gives you. This guide compares the three main approaches — H2 in-memory, real database, and Testcontainers — with honest trade-offs, code examples for each, and a practical guide to combining them for maximum coverage at minimum cost.

The Three Approaches at a Glance

H2 In-MemoryReal Database (shared)Testcontainers
SpeedFastest (<1s startup)No startup costMedium (3–5s startup)
RealismLow — H2 ≠ PostgreSQLHigh — same as productionHighest — exact same engine
IsolationPerfect — fresh DB per runPoor — shared statePerfect — fresh container
CI friendlyYesRequires shared DB serverYes (Docker required)
Catches dialect bugsNoYesYes
Setup complexityMinimalMedium (infra required)Low (just Docker)

Option 1: H2 In-Memory Database

H2 is an embedded Java database that starts in milliseconds. Spring Boot’s @DataJpaTest uses H2 by default if it’s on the classpath.

<!-- H2 in-memory: auto-detected by @DataJpaTest -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>test</scope>
</dependency>
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.beans.factory.annotation.Autowired;

// @DataJpaTest uses H2 by default — no configuration needed
@DataJpaTest
@DisplayName("ProductRepository — H2 in-memory tests")
class ProductRepositoryH2Test {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private ProductRepository productRepository;

    @Test
    @DisplayName("Saving a product persists it and assigns an ID")
    void savingProductPersistsAndAssignsId() {
        Product product = new Product(null, "Laptop", 999.00, "Electronics");
        Product saved = entityManager.persistAndFlush(product);

        assertNotNull(saved.getId(), "Auto-generated ID must be assigned");
        assertEquals("Laptop", saved.getName());
    }

    @Test
    @DisplayName("Custom query findByCategory returns matching products")
    void findByCategoryReturnsMatchingProducts() {
        entityManager.persistAndFlush(new Product(null, "Laptop",     999.0, "Electronics"));
        entityManager.persistAndFlush(new Product(null, "Headphones",  79.0, "Electronics"));
        entityManager.persistAndFlush(new Product(null, "Notebook",     5.9, "Stationery"));

        List<Product> electronics = productRepository.findByCategory("Electronics");

        assertEquals(2, electronics.size());
    }
}

When to use H2: JPQL queries, basic CRUD verification, entity mapping validation, and anything that does not depend on database-specific SQL features or constraints.

When NOT to use H2: Native SQL queries, PostgreSQL-specific types (JSONB, arrays, UUID), CTEs, window functions, specific index behaviour, or ON CONFLICT (upsert) syntax.

Continue reading Database Testing in JUnit 6: H2 vs Real DB vs Containers

JUnit 6 with Testcontainers: Real Database Integration Testing

In-memory databases like H2 are fast and convenient but they hide real-world bugs — dialect differences, missing features, and different constraint handling mean tests pass on H2 but fail against your actual PostgreSQL or MySQL database. Testcontainers solves this by spinning up a real database (or any other service) in a Docker container for your tests. This guide shows you how to integrate Testcontainers with JUnit 6 for truly reliable database integration tests.

Prerequisites

  • Docker installed and running on the test machine (or CI agent)
  • Java 11+, JUnit 6, Maven or Gradle
  • Internet access on first run (to pull Docker images)

Dependencies

<!-- Testcontainers BOM: manages all module versions -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>testcontainers-bom</artifactId>
            <version>1.20.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!-- Core Testcontainers + JUnit 5/6 integration -->
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- PostgreSQL module — swap for mysql, mariadb, mongodb, etc. -->
    <dependency>
        <groupId>org.testcontainers</groupId>
        <artifactId>postgresql</artifactId>
        <scope>test</scope>
    </dependency>

    <!-- PostgreSQL JDBC driver -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Basic: Starting a PostgreSQL Container Per Test Class

import org.junit.jupiter.api.*;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.sql.*;
import static org.junit.jupiter.api.Assertions.*;

// @Testcontainers: activates Testcontainers JUnit 6 extension
// It starts @Container fields before the tests and stops them after
@Testcontainers
@DisplayName("User Repository — PostgreSQL integration tests")
class UserRepositoryPostgresTest {

    // @Container: Testcontainers manages this container's lifecycle
    // 'static' = shared across ALL tests in this class (faster — starts once)
    // non-static = restarted before each test (slower but fully isolated)
    @Container
    static final PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:16-alpine")
            .withDatabaseName("testdb")
            .withUsername("testuser")
            .withPassword("testpass")
            .withInitScript("db/init-schema.sql"); // runs SQL on container start

    private Connection connection;
    private UserRepository userRepository;

    @BeforeEach
    void setUp() throws Exception {
        // Connect to the container using its dynamic port
        connection = DriverManager.getConnection(
            postgres.getJdbcUrl(),      // e.g. jdbc:postgresql://localhost:54321/testdb
            postgres.getUsername(),
            postgres.getPassword()
        );
        userRepository = new UserRepository(connection);
    }

    @AfterEach
    void tearDown() throws Exception {
        // Clean up test data between tests
        try (Statement stmt = connection.createStatement()) {
            stmt.execute("DELETE FROM users");
        }
        connection.close();
    }

    @Test
    @DisplayName("Saving a user persists it to PostgreSQL")
    void savingUserPersistsToPostgres() throws Exception {
        User user = new User(null, "[email protected]", "Alice");
        User saved = userRepository.save(user);

        assertNotNull(saved.getId(), "Saved user must have a generated ID");
        assertEquals("[email protected]", saved.getEmail());
    }

    @Test
    @DisplayName("Finding a user by email returns the correct record")
    void findByEmailReturnsCorrectUser() throws Exception {
        userRepository.save(new User(null, "[email protected]", "Bob"));

        Optional<User> found = userRepository.findByEmail("[email protected]");

        assertTrue(found.isPresent());
        assertEquals("Bob", found.get().getName());
    }

    @Test
    @DisplayName("Unique email constraint prevents duplicate users")
    void uniqueEmailConstraintPreventsduplicates() {
        userRepository.save(new User(null, "[email protected]", "Carol"));

        // Second save with same email should throw a constraint violation
        assertThrows(DataIntegrityViolationException.class,
            () -> userRepository.save(new User(null, "[email protected]", "Carol2")),
            "Duplicate email must be rejected by the database constraint"
        );
    }
}
Continue reading JUnit 6 with Testcontainers: Real Database Integration Testing

Creating A Simple Database Application In Visual Studio ’10 (Drag & Drop Way)

Note: This post was written in 2012 for Visual Studio 2010. The .sdf (SQL Server Compact) format used here has since been discontinued by Microsoft and is no longer supported in modern Visual Studio versions. If you’re starting fresh today, consider SQLite, LocalDB, or SQL Server Express instead.

There are already thousands of tutorials on the web for this kind of thing, and most of them are unnecessarily complicated. They use too many columns and too much code, which just ends up confusing beginners. So I’m going to show you how to build your first database VB app the simple way — three columns only (ID, Name, City), and almost everything done through drag and drop. No heavy coding needed.

Steps

Since we’re building a Windows Forms application, choose Windows Forms Application as the project template.

I’ve named the project dbapp, but you can call it whatever you like.

Continue reading Creating A Simple Database Application In Visual Studio ’10 (Drag & Drop Way)

Starting With Oracle SQL

Note: I wrote this guide back in 2012 for Oracle SQL Express Edition 11g. While the command prompt steps are still valid for Oracle databases, the specific software versions and setup screens will look completely different today!

Today, I’m going to show you how to connect to and interact with Oracle SQL directly from your Windows command prompt. You might be wondering why we don’t just use the built-in Run SQL Command Line tool from the Start menu. The big issue with that tool is that it doesn’t support basic copy and paste functionality—something that developers like you and me desperately need! So, let’s look at how to properly connect to your database using the trusty standard command prompt.

First things first, you’ll need to have Oracle SQL installed on your PC. If you don’t have it yet, you can grab it right here:

Download Oracle SQL Express Edition 11g

Oracle SQL Express is completely free to use, but Oracle does require you to log in before downloading. If you don’t have an account, just hit the sign-up button—it’s free and only takes a minute.

The download is fairly hefty at around 350MB. Once it’s finished, extract the ZIP file and run setup.exe. The installation wizard is pretty standard. If you’ve installed Windows software before, you can safely just click “Next” through most of the prompts.

During the setup process, you’ll eventually hit a screen that looks like this:

This is an important step. You’re being asked to set the password for the SYS and SYSTEM users. In Oracle, these are your master administrative accounts. If you’re just setting this up on your local machine for development and learning, I highly recommend using a simple password like admin. It’s easy to remember and quick to type. Of course, you can use whatever password you prefer. Once you’ve typed it in, click Next.

The installer will now begin copying files and setting up your database. Just sit back and wait for it to finish.

Continue reading Starting With Oracle SQL