Unittesten is een methode voor softwareverificatie waarbij de correctheid van individuele modules of codefuncties in isolatie van de rest van het systeem wordt getest. Volgens Martin Fowler, 2026 vormen unittesten de basis van CI/CD en refactoring en bieden ze snelle feedback over de werking van code. Modulair testen helpt fouten in een vroeg stadium van ontwikkeling te ontdekken, waardoor de kosten voor het oplossen ervan tientallen keren worden verlaagd.
Belangrijkste punten
Unittesten is het proces van het controleren van individuele eenheden (unit) van broncode — functies, methoden, klassen — in isolatie van de rest van het programma. Elke test voert een specifiek gebruiks scenario van de module uit en controleert of het resultaat overeenkomt met de verwachting. Unittesten worden geschreven in dezelfde programmeertaal als de hoofdcode en worden automatisch uitgevoerd in de ontwikkelomgeving of in de CI/CD-pijplijn. In tegenstelling tot integratietests interacteren unittesten niet met echte databases, bestandssystemen of netwerkdiensten.
Het hoofddoel is snelle feedback over de correctheid van code na wijzigingen. Als een ontwikkelaar een methode refactort, bevestigt de set unittesten dat het gedrag niet is verbroken. Volgens Google Testing Blog (2025) hebben projecten met een unittestdekking van meer dan 60% 2,5 keer minder productie incidenten. Extra voordelen: codedocumentatie (tests tonen hoe de API te gebruiken), vereenvoudiging van refactoring (implementatie kan worden gewijzigd met behoud van gedrag) en snelle diagnose van regressies.
Niet elke automatische test is een unittest. Criteria: één module (klasse of functie) wordt getest, externe afhankelijkheden worden vervangen door mocks of stubs, de test wordt in milliseconden uitgevoerd, vereist geen server of database. Een test die een echte database benadert, is een integratietest. Een test die een browser opent, is een E2E-test. Inzicht in de grenzen tussen testtypen is belangrijk voor de juiste verdeling van inspanningen in de testpiramide.
Kwalitatieve unittesten volgen de FIRST-principes geformuleerd door Robert C. Martin. Elke test moet Fast (snel — milliseconden), Isolated (geïsoleerd — niet afhankelijk van andere tests), Repeatable (herhaalbaar — hetzelfde resultaat op elke machine), Self-validating (zelfvaliderend — resultaat is „passed” of „failed”, zonder handmatige controle) en Timely (tijdig — geschreven vóór of gelijktijdig met code) zijn. Schending van een van de principes vermindert de testwaarde.
Standaard sjabloon voor het schrijven van unittesten. Arrange — voorbereiding van gegevens en afhankelijkheden: aanmaken van objecten, configureren van mocks, instellen van invoerparameters. Act — uitvoeren van de geteste actie: aanroepen van methode of functie. Assert — controleren van het resultaat: vergelijken van werkelijke waarde met verwachte waarde. Verdeling in drie blokken maakt de test leesbaar en begrijpelijk. Als het Assert-blok complexe logica vereist, test de test waarschijnlijk te veel tegelijk.
// Voorbeeld van unittest volgens AAA-sjabloon in Kotlin met JUnit 5
class CalculatorTest {
private lateinit var calculator: Calculator
@BeforeEach
fun setUp() {
// ARRANGE — we maken het te testen object
calculator = Calculator()
}
@Test
fun addition_shouldReturnCorrectSum() {
// ACT — we voeren de actie uit
val result = calculator.add(2, 3)
// ASSERT — we controleren het resultaat
Assertions.assertEquals(5, result)
}
}De testnaam moet beschrijven wat wordt gecontroleerd en welk resultaat wordt verwacht. Formaat: [methodName]_[scenario]_[expectedResult]. Voorbeeld: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Een goede testnaam vervangt een opmerking en geeft bij falen direct aan welke functionaliteit is verbroken. Vermijd namen als test1, checkSomething of verify — ze dragen geen informatie en bemoeilijken diagnose.
Voor isolatie van de geteste module van externe afhankelijkheden worden testdoubles gebruikt. Hoofdtypen: mocks — controleren of een bepaalde methode met de juiste parameters is aangeroepen; stubs — retourneren vooraf gedefinieerde waarden bij aanroep; fakes — vereenvoudigde implementatie van een echte component (bijv. InMemoryUserRepository in plaats van UserRepository die met database werkt). Keuze hangt af van wat moet worden gecontroleerd: toestand (stub) of interactie (mock).
| Double | Wat controleert | Voorbeeld |
|---|---|---|
| Mock | Aanroep methode met juiste parameters | userRepository.save(user) is exact 1 keer aangeroepen |
| Stub | Retourwaarde | repository.findById(1) retourneert User(id=1, name=„Test”) |
| Fake | Logica via vereenvoudigde implementatie | InMemoryMapUserRepository met HashMap i.p.v. database |
| Spy | Gedeeltelijk mocken van echt object | spy(repo).when(findById).thenReturn(user) |
Mockito is het populairste framework voor mocken in Java en Kotlin. Het maakt het mogelijk mocks te maken via mock(), retourwaarden in te stellen via when().thenReturn() en aanroepen te controleren via verify(). Moderne versies van Mockito (5.x) ondersteunen statische mocks (mockStatic) en vereenvoudigde syntaxis via BDDMockito (given-willReturn). Belangrijke regel: mock niet wat niet van jou is — maak geen mocks voor waardeobjecten en standaardbibliotheken.
// Voorbeeld van unittest met Mockito in Kotlin
class OrderServiceTest {
@Mock
private lateinit var paymentGateway: PaymentGateway
@Mock
private lateinit var userRepository: UserRepository
private lateinit var orderService: OrderService
@BeforeEach
fun init() {
MockitoAnnotations.openMocks(this)
orderService = OrderService(paymentGateway, userRepository)
}
@Test
fun processOrder_whenPaymentFails_shouldThrowException() {
// given
val user = User(id = 1, balance = 100.0)
val order = Order(amount = 200.0)
Mockito.`when`(paymentGateway.charge(any())).thenReturn(false)
Mockito.`when`(userRepository.findById(1)).thenReturn(user)
// when & then
assert Throws<PaymentException> {
orderService.processOrder(user.id, order)
}
// verify
Mockito.verify(paymentGateway).charge(any())
}
}TDD (Test-Driven Development) — methodologie waarbij de test vóór de implementatie van code wordt geschreven. Cyclus „Red-Green-Refactor”: schrijf een test die faalt (Red), schrijf minimale code om de test te laten slagen (Green), verbeter code zonder gedrag te wijzigen (Refactor). TDD garandeert dat alle code door tests wordt gedekt (dekking = 100% voor geschreven functionaliteit) en dat code testbaar is — als code moeilijk te testen is, betekent dit dat de architectuur verbetering behoeft.
Volgens onderzoek van IBM (2006-2026, longitudinale studie) maken teams die TDD gebruiken 40-80% minder defecten in productie in vergelijking met teams die tests na code schrijven. TDD verbetert ook de architectuur: de ontwikkelaar moet vóór implementatie over het API-ontwerp nadenken, wat leidt tot losse koppeling (loose coupling) en hoge cohesie (high cohesion). Een extra effect — documentatie met levende code: tests dienen als specificatie van modulegedrag die altijd actueel is.
TDD is niet altijd optimaal. UI-componenten zijn moeilijk in isolatie te testen — hiervoor zijn snapshot-tests of visuele regressietests (Percy, Chromatic) effectiever. Prototyping en onderzoek (spike solutions) vereisen geen tests. Legacy-code zonder tests is moeilijk via TDD te dekken — hier zijn eerst characterization tests nodig (tests die huidig gedrag vastleggen vóór refactoring). In deze gevallen wordt TDD niet volledig afgeschaft maar aangepast — tests worden geschreven voor gewijzigde functionaliteit, niet voor alle legacy-code.
Mobiele ontwikkeling heeft zijn eigenheid: bedrijfslogica is vaak vermengd met UI-code (Activity, ViewController, ViewModel), wat unittesten bemoeilijkt. Beste praktijk — dunne views, dikke ViewModels: verplaats alle logica van UI-componenten naar aparte klassen (UseCase, Repository, ViewModel) die gemakkelijk zonder emulator te testen zijn. Voor Android en iOS bestaan native unittest-frameworks die op JVM/Native werken zonder het apparaat te starten.
Android-unittesten worden uitgevoerd op lokale JVM zonder emulator, wat uitvoeringsnelheid garandeert — een typische test duurt minder dan 100ms. JUnit 5 is de hoofdrunner. Voor ViewModel-tests gebruikt u kotlinx-coroutines-test voor het testen van coroutines en Turbine voor het testen van StateFlow. Robolectric maakt het testen van Android-afhankelijke componenten (Context, Resources) zonder emulator mogelijk door shadow-klassen te laden. Voor Compose-tests gebruikt u Compose UI Test — maar dit zijn al UI-tests, geen unittesten.
iOS-unittesten worden geschreven in Swift met XCTest (ingebouwd in Xcode). Quick + Nimble — BDD-frameworks voor leesbaardere tests (describe/context/it). Voor mocken gebruikt u Cuckoo (generatie van mocks) of SwiftyMocky. Swift ondersteunt protocollen en dependency-injectie, wat het vervangen van afhankelijkheden vergemakkelijkt. Belangrijk punt: iOS-unittesten worden uitgevoerd op macOS-simulator, niet op een echt apparaat. Tests die hardwarefuncties (camera, Bluetooth) vereisen, zijn integratietests.
Flutter-unittesten gebruiken het flutter_test-pakket en worden uitgevoerd op Dart VM zonder emulator. Voor mocken — het mockito-pakket met codegenerator (build_runner). Widget-tests (in hetzelfde pakket) testen individuele widgets, maar vereisen rendering en werken langzamer — gebruik ze alleen voor het controleren van UI-logica. Pure Dart-logica (modellen, repositories, blobs) wordt getest als gewone Dart-tests zonder flutter_test te importeren.
// Voorbeeld van unittest op Flutter met mockito
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/annotations.dart';
@GenerateMocks([ApiClient])
import 'user_repository_test.mocks.dart';
void main() {
late MockApiClient mockApi;
late UserRepository repository;
setUp(() {
mockApi = MockApiClient();
repository = UserRepository(mockApi);
});
test('fetchUser returns user when API succeeds', () async {
// Arrange
final expectedUser = User(id: 1, name: 'Test');
when(mockApi.getUser(1))
.thenAnswer((_) async => expectedUser);
// Act
final result = await repository.fetchUser(1);
// Assert
expect(result, expectedUser);
verify(mockApi.getUser(1)).called(1);
});
}Effectief unittesten vereist discipline. Hoofdregel: test gedrag, niet implementatie. De test mag niet weten hoe de module intern is geïmplementeerd (welke privémethoden worden aangeroepen, in welke volgorde). Als de test aan implementatie is gekoppeld, breekt hij bij elke refactoring en verliest hij waarde. De test controleert een contract: bij invoer X moet uitvoer Y zijn. Uitzondering — tests voor algoritmen met kritieke prestaties, waar de volgorde van aanroepen belangrijk is.
100% dekking is een onbereikbaar en onnodig doel. Volgens Google Testing Blog (2025) is het optimale dekkingsniveau voor unittesten 70-80% van de coderegels. 100% dekking wordt vaak bereikt door getters, setters en constructors te testen, wat geen waarde toevoegt. Focus op kritieke bedrijfslogica: complexe berekeningen, validatie, foutafhandeling, randgevallen. Gebruik JaCoCo (Java), Coverage.py (Python), Istanbul (JS) voor meting en stel een drempel in CI in — build-falen bij dekking onder 60%.
Unittesten zijn de eerste fase van elke CI/CD-pijplijn. Ze worden uitgevoerd bij elke push naar de repository, vóór build en deploy. De gemiddelde uitvoeringstijd van unittesten in een project mag niet meer dan 5 minuten bedragen — als het langer duurt, zijn tests niet meer „snel” en ontwikkelaars stoppen met lokaal uitvoeren. Deel tests in snelle (unit) en langzame (integratie) en voer ze uit in verschillende fasen van de pijplijn. Gebruik parallelle uitvoering en fail-fast voor versnelling.
Veelgestelde vragen
Een unittest controleert één module in isolatie, vervangt externe afhankelijkheden door mocks. Een integratietest controleert interactie tussen meerdere echte componenten (database, API, bestandssysteem). Unittesten worden uitgevoerd in milliseconden, integratietests in seconden. In de testpiramide vormen unittesten 70%.
Keuze hangt af van platform: JUnit 5 voor Java/Kotlin, XCTest voor iOS/Swift, pytest voor Python, Jest/Vitest voor JavaScript/TypeScript, flutter_test voor Flutter. Voor mocken gebruikt u Mockito (Java), Cuckoo (iOS), unittest.mock (Python) of vitest.mock (JS). Alle moderne frameworks ondersteunen geparametriseerde tests, ingebouwde asserts en parallelle uitvoering.
Fast — test wordt in milliseconden uitgevoerd. Isolated — niet afhankelijk van andere tests en externe systemen. Repeatable — geeft hetzelfde resultaat op elke machine. Self-validating — controleert resultaat automatisch. Timely — geschreven vóór of synchroon met code. Schending van een van de principes vermindert de testefficiëntie.
Ja, verplicht. ViewModel bevat bedrijfslogica — gebeurtenisafhandeling, gegevenstransformatie, statusbeheer. Op Android gebruikt u kotlinx-coroutines-test voor coroutines en Turbine voor StateFlow-testen. Op iOS test u Combine Publishers of async/await in ViewModel. ViewModel-tests zijn pure unittesten die op JVM/macOS zonder emulator werken.
Netwerkverzoeken worden in unittesten niet uitgevoerd — ze worden vervangen door mocks van de HTTP-client. Op Android gebruikt u MockWebServer (OkHttp) — dit start een lokale HTTP-server, wat de voorkeur heeft boven mocks omdat het echte netwerkinteractie reproduceert. MockWebServer biedt isolatie zonder realisme te verliezen. Voor iOS — OHHTTPStubs of URLProtocol voor het onderscheppen en vervangen van antwoorden.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook