Enhetstestning är en metod för programvaruverifiering där man testar korrektheten hos enskilda moduler eller kodfunktioner i isolering från resten av systemet. Enligt Martin Fowler, 2026 är enhetstester grunden för CI/CD och refaktorering och ger snabb återkoppling om kodens funktionsduglighet. Modultestning hjälper till att upptäcka fel i tidiga utvecklingsskeden och minskar kostnaderna för att åtgärda dem med tiotal gånger.
Huvudpunkter
Enhetstestning är processen att kontrollera enskilda enheter av källkod — funktioner, metoder, klasser — i isolering från resten av programmet. Varje test kör ett specifikt användningsscenario för modulen och kontrollerar om resultatet motsvarar det förväntade. Enhetstester skrivs på samma programmeringsspråk som huvudkoden och körs automatiskt i utvecklingsmiljön eller i CI/CD-pipelinen. Till skillnad från integrationstester interagerar enhetstester inte med verkliga databaser, filsystem eller nätverkstjänster.
Huvudmålet är snabb återkoppling om kodens korrekthet efter ändringar. Om en utvecklare refaktorerar en metod bekräftar uppsättningen enhetstester att beteendet inte har brutits. Enligt Google Testing Blog (2025) har projekt med enhetstesttäckning över 60% 2,5 gånger färre produktionsincidenter. Ytterligare fördelar: kod dokumentation (tester visar hur API:et används), förenkling av refaktorering (implementeringen kan ändras med bibehållet beteende) och snabb diagnos av regressioner.
Inte varje automatiskt test är ett enhetstest. Kriterier: en modul (klass eller funktion) testas, externa beroenden ersätts med mockar eller stubbarna, testet körs på millisekunder, kräver inte att en server eller databas startas. Ett test som använder en verklig databas är ett integrationstest. Ett test som öppnar en webbläsare är ett E2E-test. Att förstå gränserna mellan testtyper är viktigt för korrekt fördelning av ansträngningar i testpyramiden.
Kvalitativa enhetstester följer FIRST-principerna formulerade av Robert C. Martin. Varje test bör vara Fast (snabbt — millisekunder), Isolated (isolerat — inte beroende av andra tester), Repeatable (repeterbart — samma resultat på vilken maskin som helst), Self-validating (självvaliderande — resultatet är ”passed” eller ”failed”, utan manuell kontroll) och Timely (lägligt — skrivet före eller samtidigt med koden). Brott mot någon princip minskar testets värde.
Standardmall för att skriva enhetstester. Arrange — förberedelse av data och beroenden: skapa objekt, konfigurera mockar, ställa in inparametrar. Act — utförande av den testade åtgärden: anropa metod eller funktion. Assert — kontroll av resultatet: jämföra det faktiska värdet med det förväntade. Uppdelningen i tre block gör testet läsbart och förståeligt. Om Assert-blocket kräver komplex logik testar testet troligen för många saker samtidigt.
// Exempel på enhetstest enligt AAA-mallen i Kotlin med JUnit 5
class CalculatorTest {
private lateinit var calculator: Calculator
@BeforeEach
fun setUp() {
// ARRANGE — vi skapar det testade objektet
calculator = Calculator()
}
@Test
fun addition_shouldReturnCorrectSum() {
// ACT — vi utför åtgärden
val result = calculator.add(2, 3)
// ASSERT — vi kontrollerar resultatet
Assertions.assertEquals(5, result)
}
}
Testets namn bör beskriva vad som kontrolleras och vilket resultat som förväntas. Format: [methodName]_[scenario]_[expectedResult]. Exempel: calculateTotal_whenDiscountApplied_shouldReturnDiscountedTotal. Ett bra testnamn ersätter kommentaren och vid misslyckande visar det omedelbart vilken funktionalitet som är störd. Undvik namn som test1, checkSomething eller verify — de bär ingen information och försvårar diagnos.
För att isolera den testade modulen från externa beroenden används testdoubles. Huvudtyper: mockar (mocks) — kontrollerar om en viss metod anropats med rätt parametrar; stubbarna (stubs) — returnerar fördefinierade värden vid metodanrop; fejkar (fakes) — förenklad implementering av en verklig komponent (t.ex. InMemoryUserRepository istället för UserRepository som arbetar med databas). Valet beror på vad som behöver kontrolleras: tillstånd (stub) eller interaktion (mock).
| Double | Vad kontrollerar | Exempel |
|---|---|---|
| Mock | Anrop av metod med rätt parametrar | userRepository.save(user) anropades exakt 1 gång |
| Stub | Returvärde | repository.findById(1) returnerar User(id=1, name=”Test”) |
| Fejk | Logik via förenklad implementering | InMemoryMapUserRepository med HashMap istället för databas |
| Spion | Partiell mockning av verkligt objekt | spy(repo).when(findById).thenReturn(user) |
Mockito är det populäraste ramverket för mockning i Java och Kotlin. Det möjliggör att skapa mockar via mock(), konfigurera returvärden via when().thenReturn() och kontrollera anrop via verify(). Moderna versioner av Mockito (5.x) stödjer statiska mockar (mockStatic) och förenklad syntax via BDDMockito (given-willReturn). Viktig regel: mocka inte det som inte är ditt — skapa inte mockar för värdeobjekt och standardbibliotek.
// Exempel på enhetstest med Mockito i 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) — metodik där testet skrivs före implementeringen av koden. Cykeln ”Red-Green-Refactor”: skriv ett test som misslyckas (Red), skriv minimal kod för att klara testet (Green), förbättra koden utan att ändra beteende (Refactor). TDD garanterar att all kod täcks av tester (täckning = 100% för skriven funktionalitet) och att koden är testbar — om koden är svår att testa betyder det att arkitekturen behöver förbättras.
Enligt forskning från IBM (2006-2026, longitudinell studie) gör team som använder TDD 40-80% färre defekter i produktion jämfört med team som skriver tester efter koden. TDD förbättrar också arkitekturen: utvecklaren måste tänka på API-designen före implementering, vilket leder till lös koppling (loose coupling) och hög kohesion (high cohesion). En ytterligare effekt — dokumentation med levande kod: tester tjänar som specifikation av modulens beteende som alltid är aktuell.
TDD är inte alltid optimalt. UI-komponenter är svåra att testa i isolering — för dem är snapshot-tester eller visuell regressionstestning (Percy, Chromatic) effektivare. Prototyping och forskning (spike solutions) kräver inga tester. Legacy-kod utan tester är svår att täcka genom TDD — här behövs först characterization tests (tester som fastställer nuvarande beteende före refaktorering). I dessa fall avskaffas inte TDD helt utan anpassas — tester skrivs för ändrad funktionalitet, inte för hela legacy-koden.
Mobil utveckling har sin specifikation: affärslogik är ofta blandad med UI-kod (Activity, ViewController, ViewModel), vilket försvårar enhetstestning. Bästa praxis — tunna vyer, tjocka ViewModel: flytta all logik från UI-komponenter till separata klasser (UseCase, Repository, ViewModel) som är lätta att testa utan emulator. För Android och iOS finns inbyggda ramverk för enhetstestning som körs på JVM/Native utan att starta enheten.
Android-enhetstester körs på lokal JVM utan emulator, vilket säkerställer exekveringshastighet — ett typiskt test tar mindre än 100ms. JUnit 5 är huvudkörningen. För ViewModel-tester använd kotlinx-coroutines-test för att testa korutiner och Turbine för att testa StateFlow. Robolectric gör det möjligt att testa Android-beroende komponenter (Context, Resources) utan emulator genom att ladda shadow-klasser. För Compose-tester använd Compose UI Test — men detta är redan UI-tester, inte enhetstester.
iOS-enhetstester skrivs i Swift med XCTest (inbyggt i Xcode). Quick + Nimble — BDD-ramverk för mer läsbara tester (describe/context/it). För mockning använd Cuckoo (generering av mockar) eller SwiftyMocky. Swift stödjer protokoll och dependency injection, vilket underlättar byte av beroenden. Viktig punkt: iOS-enhetstester körs på macOS-simulatorn, inte på en verklig enhet. Tester som kräver hårdvarufunktioner (kamera, Bluetooth) är integrationstester.
Flutter-enhetstester använder paketet flutter_test och körs på Dart VM utan emulator. För mockning — paketet mockito med kodgenerator (build_runner). Widget-tester (i samma paket) testar enskilda widgetar men kräver rendering och körs långsammare — använd dem endast för att kontrollera UI-logik. Ren Dart-logik (modeller, repositories, blobs) testas som vanliga Dart-tester utan att importera flutter_test.
// Exempel på enhetstest på Flutter med 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);
});
}
Effektiv enhetstestning kräver disciplin. Huvudregel: testa beteende, inte implementering. Testet bör inte veta hur modulen är implementerad internt (vilka privata metoder som anropas, i vilken ordning). Om testet är knutet till implementeringen går det sönder vid varje refaktorering och förlorar värde. Testet kontrollerar ett kontrakt: vid ingång X bör utgång Y vara. Undantag — tester för algoritmer med kritisk prestanda där anropsordningen är viktig.
100% täckning är ett ouppnåeligt och onödigt mål. Enligt Google Testing Blog (2025) är den optimala täckningsnivån för enhetstester 70-80% av kodraderna. 100% täckning uppnås ofta genom att testa getters, setters och konstruktorer, vilket inte tillför värde. Fokusera på kritisk affärslogik: komplexa beräkningar, validering, felhantering, gränsfall. Använd JaCoCo (Java), Coverage.py (Python), Istanbul (JS) för mätning och ställ in ett tröskelvärde i CI — byggnad misslyckas vid täckning under 60%.
Enhetstester är första steget i varje CI/CD-pipeline. De körs vid varje push till repot, före bygge och driftsättning. Genomsnittlig körtid för enhetstester i ett projekt bör inte överstiga 5 minuter — om det är längre slutar testerna att vara ”snabba” och utvecklare slutar köra dem lokalt. Dela upp tester i snabba (enhet) och långsamma (integration) och kör dem i olika steg i pipelinen. Använd parallell exekvering och fail-fast för snabbare resultat.
Vanliga frågor
Ett enhetstest kontrollerar en modul i isolering och ersätter externa beroenden med mockar. Ett integrationstest kontrollerar interaktion mellan flera verkliga komponenter (databas, API, filsystem). Enhetstester körs på millisekunder, integrationstester på sekunder. I testpyramiden utgör enhetstester 70%.
Valet beror på plattform: JUnit 5 för Java/Kotlin, XCTest för iOS/Swift, pytest för Python, Jest/Vitest för JavaScript/TypeScript, flutter_test för Flutter. För mockning använd Mockito (Java), Cuckoo (iOS), unittest.mock (Python) eller vitest.mock (JS). Alla moderna ramverk stödjer parametriserade tester, inbyggda asserts och parallell körning.
Fast — testet körs på millisekunder. Isolated — är inte beroende av andra tester och externa system. Repeatable — ger samma resultat på vilken maskin som helst. Self-validating — kontrollerar resultatet automatiskt. Timely — skrivet före eller synkront med koden. Brott mot någon princip minskar testningens effektivitet.
Ja, absolut. ViewModel innehåller affärslogik — händelsehantering, datatransformering, tillståndshantering. På Android använd kotlinx-coroutines-test för korutiner och Turbine för att testa StateFlow. På iOS testa Combine Publishers eller async/await i ViewModel. ViewModel-tester är rena enhetstester som körs på JVM/macOS utan emulator.
Nätverksanrop i enhetstester utförs inte — de ersätts med mockar av HTTP-klienten. På Android använd MockWebServer (OkHttp) — det startar en lokal HTTP-server, vilket är att föredra framför mockar eftersom det efterliknar verklig nätverksinteraktion. MockWebServer ger isolering utan att förlora realism. För iOS — OHHTTPStubs eller URLProtocol för att fånga upp och ersätta svar.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också