MockK: A Mocking Library for Kotlin

1. Overzicht

In deze tutorial gaan we enkele basisfuncties van de MockK-bibliotheek bekijken.

2. MockK

In Kotlin zijn alle klassen en methoden definitief. Hoewel dit ons helpt om onveranderlijke code te schrijven, veroorzaakt het ook enkele problemen tijdens het testen.

De meeste JVM-mock-bibliotheken hebben problemen met het bespotten of stoten van eindcursussen. We kunnen natuurlijk de "Open”Trefwoord voor klassen en methoden die we willen bespotten. Maar het veranderen van klassen alleen om bepaalde code te bespotten, voelt niet als de beste benadering.

Hier komt de MockK-bibliotheek, die ondersteuning biedt voor Kotlin-taalfuncties en -constructies. MockK bouwt proxy's voor bespotte klassen. Dit veroorzaakt enige prestatievermindering, maar de algemene voordelen die MockK ons biedt, zijn het waard.

3. Installatie

De installatie is zo eenvoudig mogelijk. We hoeven alleen de mockk-afhankelijkheid toe te voegen aan ons Maven-project:

 io.mockk mockk 1.9.3-test 

Voor Gradle moeten we het toevoegen als een testafhankelijkheid:

testImplementation "io.mockk: mockk: 1.9.3"

4. Basisvoorbeeld

Laten we een service maken die we graag willen bespotten:

class TestableService {fun getDataFromDb (testParameter: String): String {// querydatabase en retourneer overeenkomende waarde} fun doSomethingElse (testParameter: String): String {return "Ik wil niet!" }}

Hier is een voorbeeldtest die bespot TestableService:

@Test plezier givenServiceMock_whenCallingMockedMethod_thenCorrectlyVerified () {// gegeven val service = mockk () elke {service.getDataFromDb ("Verwachte parameter")} retourneert "Verwachte uitvoer" // wanneer val resultaat = service.getDataFromDb ("Verwachte parameter") // verifieer vervolgens {service.getDataFromDb ("Expected Param")} assertEquals ("Expected Output", resultaat)}

Om het mock-object te definiëren, hebben we de mockk () methode.

In de volgende stap hebben we het gedrag van onze mock gedefinieerd. Voor dit doel hebben we een elke blok dat beschrijft welk antwoord moet worden geretourneerd voor welke aanroep.

Ten slotte hebben we de verifiëren block om te verifiëren of de mock werd aangeroepen zoals we hadden verwacht.

5. Annotatievoorbeeld

Het is mogelijk om MockK-annotaties te gebruiken om allerlei soorten spot te maken. Laten we een service maken waarvoor twee exemplaren van onze TestableService:

class InjectTestService {lateinit var service1: TestableService lateinit var service2: TestableService fun invokeService1 (): String {return service1.getDataFromDb ("Test Param")}}

InjectTestService bevat twee velden met hetzelfde type. Het zal geen probleem zijn voor MockK. MockK probeert eigenschappen op naam te matchen en vervolgens op klasse of superklasse. Het ook heeft geen probleem met het injecteren van objecten in privévelden.

Laten we de spot drijven InjectTestService in een test met behulp van annotaties:

class AnnotationMockKUnitTest {@MockK lateinit var service1: TestableService @MockK lateinit var service2: TestableService @InjectMockKs var objectUnderTest = InjectTestService () @BeforeEach fun setUp () = MockKAnnotations.init (dit) // Test hier ...}

In het bovenstaande voorbeeld hebben we de @InjectMockKs annotatie. Dit specificeert een object waar gedefinieerde mock-ups moeten worden geïnjecteerd. Standaard worden variabelen geïnjecteerd die nog niet zijn toegewezen. We kunnen gebruiken @OverrideMockKs om velden te overschrijven die al een waarde hebben.

MockK vereist MockKAnnotations.init (...) worden aangeroepen op een object dat een variabele met annotaties declareert. Voor Junit5 kan het worden vervangen door @ExtendWith (MockKExtension :: class).

6. Spion

Spy staat het bespotten van slechts een bepaald deel van een bepaalde klasse toe. Het kan bijvoorbeeld worden gebruikt om een ​​specifieke methode te bespotten TestableService:

@Test plezier gegevenServiceSpy_whenMockingOnlyOneMethod_thenOtherMethodsShouldBehaveAsOriginalObject () {// gegeven val service = spyk () elke {service.getDataFromDb (any ())} retourneert "Mocked Output" // bij het controleren van de bespotte methode valbget ("AnyFromDb) eerst // then assertEquals ("Mocked Output", firstResult) // bij het controleren van niet bespotte methode val secondResult = service.doSomethingElse ("Any Param") // then assertEquals ("I don't want to!", secondResult)}

In het voorbeeld hebben we de spyk methode om een ​​spionageobject te maken. We hadden ook de @SpyK annotatie om hetzelfde te bereiken:

class SpyKUnitTest {@SpyK lateinit var service: TestableService // Test hier}

7. Ontspannen Mock

Een typisch bespot object zal werpen MockKException als we een methode proberen aan te roepen waarvan de retourwaarde niet is gespecificeerd.

Als we het gedrag van elke methode niet willen beschrijven, kunnen we een ontspannen mock gebruiken. Dit soort mock biedt standaardwaarden voor elke functie. Bijvoorbeeld de Draad retourneringstype retourneert een lege Draad. Hier is een kort voorbeeld:

@Test plezier gegevenRelaxedMock_whenCallingNotMockedMethod_thenReturnDefaultValue () {// gegeven val service = mockk (relaxed = true) // wanneer val resultaat = service.getDataFromDb ("Any Param") // dan assertEquals ("", resultaat)}

In het voorbeeld hebben we de mockk methode met de ontspannen attribuut om een ​​ontspannen mock-object te creëren. We hadden ook de @RTLnieuws annotatie:

class RelaxedMockKUnitTest {@RelaxedMockK lateinit var service: TestableService // Test hier}

8. Object bespotten

Kotlin biedt een gemakkelijke manier om een ​​singleton te declareren door de voorwerp trefwoord:

object TestableService {fun getDataFromDb (testParameter: String): String {// query database en retourneer overeenkomende waarde}}

De meeste spotbibliotheken hebben echter een probleem met het bespotten van Kotlin singleton-instanties. Daarom biedt MockK de mockkObject methode. Laten we kijken:

@Test fun givenObject_whenMockingIt_thenMockedMethodShouldReturnProperValue () {// gegeven mockkObject (TestableService) // bij het aanroepen van niet bespotte methode val firstResult = service.getDataFromDb ("Any Param") // retourneer vervolgens het echte antwoord assertEquals (/ * DB-resultaat *) // bij het aanroepen van de mocked-methode retourneert elke {service.getDataFromDb (any ())} "Mocked Output" val secondResult = service.getDataFromDb ("Any Param") // retourneert vervolgens een nepantwoord assertEquals ("Mocked Output", secondResult)}

9. Hiërarchisch bespotten

Een andere handige functie van MockK is de mogelijkheid om hiërarchische objecten te bespotten. Laten we eerst een hiërarchische objectstructuur maken:

class Foo {lateinit var naam: String lateinit var bar: Bar} class Bar {lateinit var bijnaam: String}

De Foo class bevat een veld van het type Bar. Nu kunnen we de structuur in slechts één eenvoudige stap bespotten. Laten we de spot drijven met naam en bijnaam velden:

@Test plezier gegevenHierarchicalClass_whenMockingIt_thenReturnProperValue () {// gegeven val foo = mockk {elke {naam} retourneert "Karol" elke {bar} retourneert mockk {elke {bijnaam} retourneert "Tomaat"}} // wanneer val name = foo.name val nickname = foo.bar.nickname // dan assertEquals ("Karol", naam) assertEquals ("Tomato", nickname)}

10. Parameters vastleggen

Als we de parameters moeten vastleggen die aan een methode zijn doorgegeven, kunnen we CapturingSlot of MutableList. Het is handig als we wat aangepaste logica in een antwoord block of we hoeven alleen de waarde van de doorgegeven argumenten te verifiëren. Hier is een voorbeeld van CapturingSlot:

@Test plezier gegevenMock_whenCapturingParamValue_thenProperValueShouldBeCaptured () {// gegeven waarde service = mockk () val slot = slot () elke {service.getDataFromDb (capture (slot))} retourneert "Verwachte uitvoer" // wanneer service.getDataFromDb ("Verwachte parameter" ) // then assertEquals ("Verwachte parameter", slot.captured)}

MutableList kan worden gebruikt om specifieke argumentwaarden vast te leggen en op te slaan voor alle methode-aanroepen:

@Test plezier gegevenMock_whenCapturingParamsValues_thenProperValuesShouldBeCaptured () {// gegeven waarde service = mockk () val lijst = mutableListOf () elke {service.getDataFromDb (capture (lijst))} retourneert "Verwachte uitvoer" // wanneer service.getDataFromDb ("Expected ParamDb (" Expected ParamDb) ") service.getDataFromDb (" Verwachte parameter 2 ") // then assertEquals (2, lijst.size) assertEquals (" Verwachte parameter 1 ", lijst [0]) assertEquals (" Verwachte parameter 2 ", lijst [1])}

11. Conclusie

In dit artikel hebben we de belangrijkste features van MockK besproken. MockK is een krachtige bibliotheek voor de Kotlin-taal en biedt veel handige functies. Voor meer informatie over MockK kunnen we de documentatie op de MockK-website raadplegen.

Zoals altijd is de gepresenteerde voorbeeldcode beschikbaar op GitHub.