Skip to main content

Oefeningen: Integration Testing

Werk de oefeningen in volgorde. Gebruik bij elke oefening echte klassen voor eigen code. Mock alleen externe diensten zoals betaalgateways.


Startpakket downloaden

De download wordt geladen…

Hierin staat alles wat je in de vorige lessen gebouwd hebt, samen met de code die je tijdens de theorie van deze les opbouwt. Wat je in de oefeningen zelf moet schrijven, staat erin als skelet met de melding // jouw code hier.

De webshop zit erbij. Je hoeft geen Razor te kennen: start hem met dotnet run --project ShopWave.Web en open http://localhost:5000. Zo zie je meteen wat je code doet.


Oefening 1: CheckoutService integreren

Leerdoel: je schrijft een integration test waarbij meerdere eigen klassen samenwerken zonder mocks voor eigen code.

Moeilijkheidsgraad: basis

Startcode

Voeg deze klasse toe aan ShopWave. Je hoeft hem niet aan te passen.

Let op: deze CheckoutService vervangt de klasse met dezelfde naam uit les 1. Die oude versie rekende met CalculateFinalTotal; deze rekent af vanuit het winkelmandje. Verwijder daarom de oude CheckoutService en ook ShopWave.Tests/CheckoutServiceTests.cs, anders compileert je project niet meer. In het startpakket is dat al gebeurd.

namespace ShopWave
{
public class CheckoutService
{
private readonly CartService cartService;
private readonly IPaymentGateway gateway;

public CheckoutService(CartService cartService, IPaymentGateway gateway)
{
this.cartService = cartService;
this.gateway = gateway;
}

public string Checkout()
{
double amount = cartService.Total;
string result;

if (amount <= 0)
{
result = "Mandje is leeg";
}
else
{
bool success = gateway.ProcessPayment(amount);
result = success ? "Betaling geslaagd" : "Betaling mislukt";
}

return result;
}
}
}

Je hebt ook de CartService en CouponService uit les 3 nodig. Gebruik de versie met ICouponService-constructor.

Opdracht

Schrijf integration tests voor de volledige checkout-flow. Gebruik echte CartService- en CouponService-instanties. Mock alleen IPaymentGateway.

Test minstens de volgende scenario's:

ScenarioVerwacht resultaat
Mandje met één artikel, betaling geslaagd"Betaling geslaagd"
Leeg mandje"Mandje is leeg"
Artikel toevoegen met geldige coupon, controleer het juiste bedrag naar de gatewaybedrag na korting
Betaling mislukt"Betaling mislukt"

Controleer bij het couponscenario via mockGateway.Verify(...) of het exacte bedrag na korting naar de gateway gestuurd wordt.

Vastgelopen?

Hulp bij Oefening 1: CheckoutService integreren

klik voor hulp

De assistent wordt geladen…


Oefening 2: DiscountCalculator integreren

Leerdoel: je test de samenwerking tussen CartService en een berekeningsklasse zonder mock.

Moeilijkheidsgraad: basis

Startcode

DiscountCalculator bestaat al sinds les 1. Voeg de methode Apply eraan toe; laat ApplyDiscount staan, want de tests van les 1 gebruiken die nog.

namespace ShopWave
{
public class DiscountCalculator
{
// ApplyDiscount uit les 1 blijft staan.

public double Apply(double amount, int discountPercent)
{
if (discountPercent < 0 || discountPercent > 100)
{
throw new ArgumentOutOfRangeException(
nameof(discountPercent),
"Kortingspercentage moet tussen 0 en 100 liggen.");
}

return amount * (1 - discountPercent / 100.0);
}
}
}

Pas CartService aan zodat die DiscountCalculator gebruikt in de Total-berekening als er een coupon is toegepast:

// Constructor uitbreiden
public CartService(ICouponService couponService, DiscountCalculator discountCalculator)
{
items = new Dictionary<string, CartItem>();
this.couponService = couponService;
this.discountCalculator = discountCalculator;
couponDiscount = 0;
}

// Total aanpassen
public double Total
{
get
{
double subtotal = 0;

foreach (CartItem item in items.Values)
{
subtotal += item.Price * item.Quantity;
}

return couponDiscount > 0
? discountCalculator.Apply(subtotal, couponDiscount)
: subtotal;
}
}

Opdracht

Schrijf integration tests waarbij CartService, CouponService en DiscountCalculator alle drie echt zijn. Mock niets van eigen code.

Test minstens:

ScenarioVerwacht resultaat
Artikel 100.00, coupon ZOMER10 (10%), DiscountCalculator toegepast90.00
Artikel 100.00, ongeldige coupon100.00
Artikel 50.00, coupon WELKOM20 (20%)40.00
Twee artikelen totaal 200.00, coupon TROUWE5 (5%)190.00

Wat je controleert: het eindtotaal is correct na de volledige keten van CartService.ApplyCoupon via CouponService via DiscountCalculator.Apply.

Vastgelopen?

Hulp bij Oefening 2: DiscountCalculator integreren

klik voor hulp

De assistent wordt geladen…


Oefening 3: de volledige bestelflow

Leerdoel: je test een end-to-end scenario door alle eigen klassen samen te laten werken.

Moeilijkheidsgraad: gemiddeld

Startcode

Je hebt de volgende klassen nodig uit les 1, les 3 en oefening 2:

  • CartService (met ICouponService en DiscountCalculator)
  • CouponService (implementeert ICouponService)
  • DiscountCalculator
  • OrderService (met IPaymentGateway, IStockService, ICouponService)

Opdracht

Schrijf integration tests voor de volledige flow: een klant vult een mandje, past een coupon toe en plaatst een bestelling via OrderService.

De flow:

  1. Maak een CartService aan met een echte CouponService en DiscountCalculator
  2. Voeg artikelen toe aan het mandje
  3. Pas een coupon toe via CartService.ApplyCoupon
  4. Haal het totaal op via CartService.Total
  5. Roep OrderService.PlaceOrder aan met dat totaal

Test minstens:

ScenarioVerwacht resultaat
Geldige coupon ZOMER10 (10%), basisprijs 100.00, betaling geslaagd"Bestelling bevestigd", gateway ontvangt 90.00
Ongeldige coupon, basisprijs 100.00, betaling geslaagd"Bestelling bevestigd", gateway ontvangt 100.00
Geen coupon, meerdere artikelen, betaling geslaagd"Bestelling bevestigd", gateway ontvangt correct totaal
Product niet op voorraad"Product niet beschikbaar"

Controleer via Verify welk bedrag precies naar ProcessPayment gaat.

Vastgelopen?

Hulp bij Oefening 3: de volledige bestelflow

klik voor hulp

De assistent wordt geladen…


Oefening 4: de callback-techniek

Leerdoel: je past de callback-techniek toe om een gegenereerde waarde op te vangen in een integration test.

Moeilijkheidsgraad: uitdaging

Startcode

Voeg deze klasse toe aan ShopWave:

namespace ShopWave
{
public class OrderConfirmationService
{
private readonly Action<string> onConfirmationCodeGenerated;

public OrderConfirmationService()
{
onConfirmationCodeGenerated = null;
}

public OrderConfirmationService(Action<string> onConfirmationCodeGenerated)
{
this.onConfirmationCodeGenerated = onConfirmationCodeGenerated;
}

public string GenerateConfirmationCode(int orderId)
{
string code = $"ORD-{orderId:D6}-{Guid.NewGuid().ToString("N")[..4].ToUpper()}";

if (onConfirmationCodeGenerated != null)
{
onConfirmationCodeGenerated(code);
}

return code;
}

public bool ValidateCode(string code)
{
return code != null && code.StartsWith("ORD-") && code.Length >= 12;
}
}
}

Opdracht

Schrijf integration tests die de samenwerking tussen OrderConfirmationService en zijn consumers testen. Gebruik de callback-techniek om de gegenereerde code op te vangen.

Test minstens:

ScenarioWat je verifieert
Code genereren voor orderId 1code start met "ORD-", heeft het juiste formaat
Callback ontvangt exact dezelfde waarde als de returnwaardecapturedCode == result
Gegenereerde code doorsturen naar ValidateCodeValidateCode geeft true terug
Twee opeenvolgende codes zijn uniekcode1 != code2

Verwachte structuur:

string capturedCode = string.Empty;

OrderConfirmationService service = new OrderConfirmationService(
onConfirmationCodeGenerated: code => { capturedCode = code; });

string result = service.GenerateConfirmationCode(1);

// Gebruik capturedCode en result in je assertions
Vastgelopen?

Hulp bij Oefening 4: de callback-techniek

klik voor hulp

De assistent wordt geladen…


Oefening 5: Reflectie

Beantwoord deze vragen voor jezelf voor je de oplossingen bekijkt.

  1. In oefening 2 gebruik je drie echte klassen. Je hebt voor elk van die klassen ook unit tests geschreven in les 1 en les 3. Wat testen de integration tests in oefening 2 dat de unit tests niet testen?

  2. Wanneer is het correct om een klasse te mocken in een integration test? Geef twee concrete voorbeelden uit de oefeningen.

  3. In oefening 4 gebruik je een callback om de bevestigingscode op te vangen. Waarom is dit beter dan een publieke property ConfirmationCode toevoegen aan de klasse?

  4. Je schrijft een integration test en hij faalt. De unit tests voor alle betrokken klassen slagen. Wat zijn mogelijke oorzaken van de fout?


Controleer je werk in de webshop

Start de webshop met dotnet run --project ShopWave.Web en open http://localhost:5000. Zo zie je je eigen code draaien in plaats van alleen een groene testbalk.

Wat je doetWat je ziet als je code klopt
Vul je mandje en ga naar BestellenHet bedrag komt uit CartService.Total, niet meer uit een los product
Reken een leeg mandje afMandje is leeg uit jouw CheckoutService.Checkout()
Reken een gevuld mandje afBetaling geslaagd en een bevestigingscode in de vorm ORD-000001-3CED
Bestel nog eensEen andere bevestigingscode: OrderConfirmationService genereert elke keer een nieuwe

Onder elk resultaat staat uit welke klasse het komt. Zie je iets anders dan hierboven, dan weet je meteen welke methode je moet nakijken.