Unit Testing - Hoofdstuk 9

Ingediend door Dirk Hornstra op 27-jan-2022 21:44

Na hoofdstuk 1: link, hoofdstuk 2: link, hoofdstuk 3: link, hoofdstuk 4: link, hoofdstuk 5: link, hoofdstuk 6: link, hoofdstuk 7: link en hoofdstuk 8: link is het nu tijd voor hoofdstuk 9.

De Timo-samenvatting

Maak alleen mocks van niet beheerde afhankelijkheden. Je kunt een Mock-framework gebruiken (Moq), maar ook zelf mocks schrijven (een spy). Controleer het aantal aanroepen (moet je 1x een POST doen en niet meerdere keren, dan moet je een Times.Once valideren), verwacht de aanroepende partij maximaal 1 GET request, valideer dan ook dat dit maximaal 1x gebeurt. Maak Adapter-classes voor code voor andere partijen. Hierdoor zijn aanpassingen in die externe code sneller in jouw code aan te passen (alleen de Adapter aanpassen) en kun je ook functienamen, parameters van die externe bibliotheek in eigen functies in jouw Adapter beter laten matchen met het project waar je deze gebruikt.

En dan nu het complete verhaal:

In hoofdstuk 5 is gezegd dat een mock een soort "test-double" is. Dus niet de echte Tom Cruise, zijn dubbelganger die de stunts doet. In hoofdstuk 8 is benoemd dat mocks alleen gebruikt worden voor niet beheerde afhankelijkheden (deze interacties zijn extern waar te nemen). Als je mocks voor andere zaken gebruikt, maak je tests die breekbaar zijn, als er ook maar iets in je code gerefactort wordt, gaat dit hoogst waarschijnlijk als eerste kapot. In dit hoofdstuk krijgen we tips en trucks hoe je mocks het beste kunt gebruiken.

Het maximaliseren van de waarde van mocks.

Het boek gaat door met het voorbeeld van het vorige hoofdstuk, in het CRM kan het e-mailadres van iemand aangepast worden. We zien de code van de controller. In deze code is nu een class EventDispatcher toegevoegd die zorgt dat via een messagebus berichten verzonden worden, deze code zat eerst in de controller. En we zien de IDomainLogger die voor het loggen gebruikt wordt.

We gaan eerst naar de IMessageBus-interface kijken. Die blijkt een wrapper om een IBus-interface te zijn, een SDK van de leverancier. Dus wat wil je eigenlijk testen (of mocken)? De IBus, want die doet dingen, de IMessageBus is er alleen maar een "schil omheen". Die wordt in de class in een concrete class gezet en zo test je de IBus. Hierdoor gebruik je de IMessageBus niet meer (die was bedoeld voor implementatie en mocken), dus deze kun je nu verwijderen omdat er maar 1 implementatie van is. En in het vorige hoofdstuk hebben we gezegd: een interface mag/kan bestaan als er minimaal 2 concrete implementaties van gemaakt worden.

Vervangen van een mock met een spion, in plaats van een object uit een Mock-library te gebruiken, maak je zelf die mock, dat wordt een "handgeschreven mock" genoemd: een spion. De termen worden vaak door elkaar gebruikt, dus je zou jouw MessageBusSpy ook MessageBusMock mogen noemen.

Vervolgens wordt naar de DomainLogger gekeken, met de vraag of je hier zou moeten focussen op ILogger (net zoals je met de messagebus gedaan hebt). Omdat het in de messagebus essentieel is dat de tekst correct overgezonden wordt doe je dat daar wel, maar hoe de tekst gelogd gaat worden in de ILogger maakt ons niet zoveel uit: zolang er maar gelogd wordt, dat is het belangrijkste.

Mocks zijn alleen voor integratie-testen, dus alleen voor de code van controllers. Het is niet noodzakelijk om "maximaal 1 mock per test" te hebben, er mogen best meerdere zijn, het gaat erom dat je "1 unit van gedrag" test en daar mogen best meerdere mocks in voorkomen.

Controleer het aantal "calls".

In het gebied van communicatie met niet beheerde afhankelijkheden geldt dat je een aantal zaken moet controleren, namelijk het aanwezig zijn van verwachte aanroepen en het afwezig zijn van onverwachte aanroepen. Jouw applicatie mag geen berichten "vergeten" te sturen naar het externe systeem waar die berichten wel verwacht worden en zou geen berichten moeten versturen die niet verwacht worden. We zien het voorbeeld met een mail die verzonden wordt en de parameter Times.Once om te controleren dat die mail 1x verzonden wordt (en niet 50x). Ook kun je in Moq daarna nog een mockObject.VerifyNoOtherCalls(); aanroepen om te valideren dat als een mail verzonden wordt die niet alsnog later nog een keer verzonden wordt.

Mock alleen types die jij beheert.

Steve Freeman en Natr Pryce kwamen met deze uitspraak. Deze richtlijn zegt dat je zelf adapters moet schrijven op code van andere partijen (nuget-packages e.d.), waardoor je mocks maakt van jouw adapters. Hun argumenten zijn:

  • jij weet niet exact hoe die code werkt en gebouwd is.
  • ook als die codebibliotheek al interfaces aanbiedt, is het risicovol om die te gebruiken omdat je niet zeker weet of jouw mock exact doet wat de codebibliotheek doet.
  • adapters zorgen voor abstractie van niet essentiële technische details en geven meer het gebruik van de code in jouw applicatie weer.


De auteur is het hier volledig mee eens en verwijst nog naar het boek Domain Driven Design van Eric Evans. Zo wordt IBus nog even genoemd, als de externe bibliotheek compleet over de kop gegooid wordt hoef je alleen je Adapter-class aan te passen en niet 100 plekken in je code. Wel noemt hij dat dit niet van toepassing is op "in proces dependencies". Dus gebruik je Entity-Framework, Newtonsoft JSON dan hoef je daar geen adapter-classes voor te maken, jij beïnvloedt zelf of je versies bij gaat werken.