Testen beheersen: JUnit, Mockito en integratietesten · Les

Database-interacties testen

Schrijf integratietests voor lagen voor gegevenstoegang en zorg voor correcte interactie met relationele databases.

Les 3 van 412 stappen

Database-interacties testen is een gratis Testen beheersen: JUnit, Mockito en integratietesten-les op CoddyKit. Dit is les 3 van 4. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Testen beheersen: JUnit, Mockito en integratietesten. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Testen beheersen: JUnit, Mockito en integratietesten bevat in totaal 4 lessen.

Waarom database-interacties testen?

Database-interacties vormen een cruciaal onderdeel van de meeste applicaties. Door deze interacties te testen, zorg je ervoor dat je app gegevens betrouwbaar opslaat, ophaalt en bijwerkt.

  • Gegevensintegriteit: garandeert dat gegevens correct worden opgeslagen.
  • Bedrijfslogica: controleert bedrijfsregels die betrekking hebben op gegevens.
  • Foutpreventie: vangt problemen op voordat ze productie bereiken.

De uitdagingen van echte databases

Rechtstreeks testen met een echte productiedatabase kan lastig zijn en tot onbetrouwbare tests leiden:

  • Langzaam: echte databases voegen aanzienlijk veel tijd toe aan testsuites.
  • Statusafhankelijk: tests kunnen achtergebleven gegevens veroorzaken die volgende tests beïnvloeden.
  • Complexe configuratie: vereist een actieve externe service en specifieke instellingen.
  • Isolatie: het is moeilijk om te garanderen dat elke test in een geïsoleerde omgeving wordt uitgevoerd.

In-memorydatabases bieden uitkomst

Om deze uitdagingen te overwinnen, gebruiken we vaak in-memorydatabases voor integratests. Dit zijn lichtgewicht databases die volledig in het geheugen van je applicatie draaien.

  • Snel: geen schijf-I/O en snel opstarten en afsluiten.
  • Geïsoleerd: elke testrun kan beginnen met een nieuwe, lege database.
  • Eenvoudige configuratie: vaak zijn alleen een afhankelijkheid en een verbindings-URL nodig.

Populaire keuzes zijn H2, HSQLDB en Apache Derby.

Je H2-database instellen

Laten we bekijken hoe eenvoudig het is om met standaard-JDBC verbinding te maken met een H2-in-memorydatabase. Deze werkt net als een gewone database, maar bevindt zich in het geheugen van je applicatie.

Je hebt de H2-afhankelijkheid nodig, bijvoorbeeld Maven: com.h2database:h2.

import java.sql.*;

public class H2Demo {
  public static void main(String[] args) throws SQLException {
    String jdbcUrl = "jdbc:h2:mem:testdb";
    String username = "sa";
    String password = "";

    try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password)) {
      Statement stmt = conn.createStatement();
      stmt.execute("CREATE TABLE products (id INT PRIMARY KEY, name VARCHAR(255))");
      stmt.executeUpdate("INSERT INTO products (id, name) VALUES (1, 'Laptop')");

      ResultSet rs = stmt.executeQuery("SELECT * FROM products");
      if (rs.next()) {
        System.out.println("Product: " + rs.getString("name"));
      }
    }
  }
}

Introductie tot de gegevenslaag

Je applicatie gebruikt doorgaans het patroon Data Access Object (DAO) of Repository om met de database te communiceren. Deze laag verbergt de details van JDBC of een ORM op laag niveau.

We gebruiken een eenvoudige ProductRepository om Product-objecten te beheren.

public class Product {
  private int id;
  private String name;

  public Product(int id, String name) {
    this.id = id;
    this.name = name;
  }

  public int getId() { return id; }
  public String getName() { return name; }
}

import java.util.List;

public interface ProductRepository {
  void save(Product product);
  Product findById(int id);
  List<Product> findAll();
}

Een eenvoudige repository implementeren

Hier zie je een basisimplementatie van onze ProductRepository met onbewerkte JDBC. In een echte applicatie zou je mogelijk Spring's JdbcTemplate of een ORM zoals Hibernate gebruiken.

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

// Product class and ProductRepository interface as defined previously

public class JdbcProductRepository implements ProductRepository {
  private Connection conn;

  public JdbcProductRepository(Connection conn) {
    this.conn = conn;
    try (Statement stmt = conn.createStatement()) {
      stmt.execute("CREATE TABLE IF NOT EXISTS products (id INT PRIMARY KEY, name VARCHAR(255))");
    } catch (SQLException e) { throw new RuntimeException(e); }
  }

  @Override
  public void save(Product product) {
    String sql = "INSERT INTO products (id, name) VALUES (?, ?)";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
      ps.setInt(1, product.getId());
      ps.setString(2, product.getName());
      ps.executeUpdate();
    } catch (SQLException e) { throw new RuntimeException(e); }
  }

  @Override
  public Product findById(int id) {
    String sql = "SELECT id, name FROM products WHERE id = ?";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
      ps.setInt(1, id); ResultSet rs = ps.executeQuery();
      if (rs.next()) { return new Product(rs.getInt("id"), rs.getString("name")); }
    } catch (SQLException e) { throw new RuntimeException(e); }
    return null;
  }

  @Override
  public List<Product> findAll() {
    List<Product> products = new ArrayList<>();
    String sql = "SELECT id, name FROM products";
    try (Statement stmt = conn.createStatement()) {
      ResultSet rs = stmt.executeQuery(sql);
      while (rs.next()) {
        products.add(new Product(rs.getInt("id"), rs.getString("name")));
      }
    } catch (SQLException e) { throw new RuntimeException(e); }
    return products;
  }
}

De repository testen - configuratie

Laten we nu een eenvoudig programma schrijven om onze JdbcProductRepository te testen. We stellen speciaal voor deze testrun een H2-in-memorydatabase in.

Let erop dat we een nieuwe databaseverbinding voor onze repository maken, zodat de isolatie behouden blijft.

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

// Product class (simplified for mobile)
class Product {
  private int id; private String name;
  public Product(int id, String name) { this.id = id; this.name = name; }
  public int getId() { return id; }
  public String getName() { return name; }
}

// ProductRepository interface
interface ProductRepository {
  void save(Product product);
  Product findById(int id);
  List<Product> findAll();
}

// JdbcProductRepository (simplified for mobile)
class JdbcProductRepository implements ProductRepository {
  private Connection conn;
  public JdbcProductRepository(Connection conn) {
    this.conn = conn;
    try (Statement stmt = conn.createStatement()) {
      stmt.execute("CREATE TABLE IF NOT EXISTS products (id INT PRIMARY KEY, name VARCHAR(255))");
    } catch (SQLException e) { throw new RuntimeException(e); }
  }
  @Override public void save(Product product) {
    String sql = "INSERT INTO products (id, name) VALUES (?, ?)";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
      ps.setInt(1, product.getId()); ps.setString(2, product.getName());
      ps.executeUpdate();
    } catch (SQLException e) { throw new RuntimeException(e); }
  }
  @Override public Product findById(int id) { /* ... omitted ... */ return null; }
  @Override public List<Product> findAll() { /* ... omitted ... */ return new ArrayList<>(); }
}

public class ProductRepositoryTestRunner {
  public static void main(String[] args) throws SQLException {
    String jdbcUrl = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";
    try (Connection conn = DriverManager.getConnection(jdbcUrl, "sa", "")) {
      System.out.println("DB connection established.");
      ProductRepository repository = new JdbcProductRepository(conn);

      Product laptop = new Product(1, "Laptop");
      repository.save(laptop);
      System.out.println("Saved product: " + laptop.getName());

      Product foundProduct = repository.findById(1);
      System.out.println("Found product: " + (foundProduct != null ? foundProduct.getName() : "None"));

      List<Product> allProducts = repository.findAll();
      System.out.println("Total products: " + allProducts.size());
    }
  }
}

De databasestatus controleren

In een echte test is alleen iets afdrukken niet voldoende. Je moet controleren of de databasestatus na een bewerking overeenkomt met je verwachtingen. Zo bevestig je dat je DAO-methoden correct werken.

Laten we enkele eenvoudige controles toevoegen om de resultaten te verifiëren.

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

// Product class (simplified for mobile)
class Product {
  private int id; private String name;
  public Product(int id, String name) { this.id = id; this.name = name; }
  public int getId() { return id; }
  public String getName() { return name; }
}

// ProductRepository interface
interface ProductRepository {
  void save(Product product); Product findById(int id); List<Product> findAll();
}

// JdbcProductRepository (simplified for mobile)
class JdbcProductRepository implements ProductRepository {
  private Connection conn;
  public JdbcProductRepository(Connection conn) { /* ... omitted ... */ this.conn = conn; }
  @Override public void save(Product product) { /* ... omitted ... */ }
  @Override public Product findById(int id) {
    String sql = "SELECT id, name FROM products WHERE id = ?";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
      ps.setInt(1, id); ResultSet rs = ps.executeQuery();
      if (rs.next()) { return new Product(rs.getInt("id"), rs.getString("name")); }
    } catch (SQLException e) { throw new RuntimeException(e); }
    return null;
  }
  @Override public List<Product> findAll() {
    List<Product> products = new ArrayList<>();
    String sql = "SELECT id, name FROM products";
    try (Statement stmt = conn.createStatement()) {
      ResultSet rs = stmt.executeQuery(sql);
      while (rs.next()) { products.add(new Product(rs.getInt("id"), rs.getString("name"))); }
    } catch (SQLException e) { throw new RuntimeException(e); }
    return products;
  }
}

public class ProductRepositoryAssertions {
  public static void main(String[] args) throws SQLException {
    String jdbcUrl = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";
    try (Connection conn = DriverManager.getConnection(jdbcUrl, "sa", "")) {
      ProductRepository repository = new JdbcProductRepository(conn);

      Product laptop = new Product(1, "Laptop");
      repository.save(laptop);

      // Assertion 1: Check if product was saved
      Product found = repository.findById(1);
      if (found != null && found.getName().equals("Laptop")) {
        System.out.println("Test Passed: Product saved & found.");
      } else { System.out.println("Test Failed: Product not found or name mismatch."); }

      // Assertion 2: Check total count
      List<Product> all = repository.findAll();
      if (all.size() == 1) {
        System.out.println("Test Passed: Correct product count.");
      } else { System.out.println("Test Failed: Incorrect product count."); }
    }
  }
}

Schone tests garanderen met transacties

Voor een robuustere testisolatie kun je, vooral bij frameworks zoals Spring, transacties gebruiken.

Als je je testmethoden annoteert met @Transactional, worden wijzigingen aan de database binnen die test automatisch teruggedraaid nadat de test is voltooid. Zo blijft de database schoon voor de volgende test.

  • Automatisch opruimen: handmatige DELETE-instructies zijn niet nodig.
  • Isolatie: elke test wordt uitgevoerd alsof het de enige test is.
  • Sneller: terugdraaien is vaak sneller dan verwijderen en opnieuw invoegen.

Best practices voor databasetests

Overweeg deze tips om je database-integratests effectief en onderhoudbaar te maken:

  • Gebruik in-memorydatabases: voor snelheid en isolatie.
  • Minimale configuratie: maak alleen tabellen en gegevens aan die nodig zijn voor de specifieke test.
  • Transactionele tests: draai wijzigingen automatisch terug.
  • Duidelijke controles: controleer de verwachte gegevens en status.
  • Richt je op DAO: test hier de gegevenslaag, niet de bedrijfslogica.

Korte controle: databasetests

Wat is een belangrijk voordeel van het gebruik van een in-memorydatabase zoals H2 voor integratests?

Samenvatting: database-interacties testen

In deze les hebben we geleerd hoe je database-interacties effectief test.

  • We hebben de uitdagingen van testen met echte databases begrepen.
  • We hebben in-memorydatabases zoals H2 ontdekt als een snelle en geïsoleerde oplossing.
  • We hebben gezien hoe je programmatisch een H2-database instelt en gebruikt.
  • We hebben geoefend met het schrijven van tests voor een Data Access Object (DAO), waarbij we gegevensintegriteit en correct gedrag waarborgen.
  • We hebben kennisgemaakt met transactionele tests voor automatisch opruimen.

Blijf oefenen om ervoor te zorgen dat de gegevenslaag van je applicatie rotsvast is!

Gratis beginnen

Leer Testen beheersen: JUnit, Mockito en integratietesten met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
12
Lessen
48

Veelgestelde vragen

Is de les “Database-interacties testen” gratis?

Ja — je kunt hier op het web alle 3 lessen van het leerpad Testen beheersen: JUnit, Mockito en integratietesten, waaronder “Database-interacties testen”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus Testen beheersen: JUnit, Mockito en integratietesten bevat in totaal 4 lessen.

Wat leer ik in “Database-interacties testen”?

Schrijf integratietests voor lagen voor gegevenstoegang en zorg voor correcte interactie met relationele databases. Je oefent met Testen beheersen: JUnit, Mockito en integratietesten door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Testen beheersen: JUnit, Mockito en integratietesten te beginnen?

Ervaring vooraf is niet nodig. Testen beheersen: JUnit, Mockito en integratietesten op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 3 van 4.

Hoe lang duurt de les “Database-interacties testen”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Testen beheersen: JUnit, Mockito en integratietesten?

Ja. Elke les over Testen beheersen: JUnit, Mockito en integratietesten bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Unit- versus integratietests
  2. Integratietests instellen
  3. Database-interacties testen
  4. Externe API's testen met WireMock
← Terug naar Testen beheersen: JUnit, Mockito en integratietesten