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
CheckoutServicevervangt de klasse met dezelfde naam uit les 1. Die oude versie rekende metCalculateFinalTotal; deze rekent af vanuit het winkelmandje. Verwijder daarom de oudeCheckoutServiceen ookShopWave.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:
| Scenario | Verwacht 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 gateway | bedrag 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:
| Scenario | Verwacht resultaat |
|---|---|
| Artikel 100.00, coupon ZOMER10 (10%), DiscountCalculator toegepast | 90.00 |
| Artikel 100.00, ongeldige coupon | 100.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(metICouponServiceenDiscountCalculator)CouponService(implementeertICouponService)DiscountCalculatorOrderService(metIPaymentGateway,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:
- Maak een
CartServiceaan met een echteCouponServiceenDiscountCalculator - Voeg artikelen toe aan het mandje
- Pas een coupon toe via
CartService.ApplyCoupon - Haal het totaal op via
CartService.Total - Roep
OrderService.PlaceOrderaan met dat totaal
Test minstens:
| Scenario | Verwacht 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:
| Scenario | Wat je verifieert |
|---|---|
| Code genereren voor orderId 1 | code start met "ORD-", heeft het juiste formaat |
| Callback ontvangt exact dezelfde waarde als de returnwaarde | capturedCode == result |
Gegenereerde code doorsturen naar ValidateCode | ValidateCode geeft true terug |
| Twee opeenvolgende codes zijn uniek | code1 != 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.
-
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?
-
Wanneer is het correct om een klasse te mocken in een integration test? Geef twee concrete voorbeelden uit de oefeningen.
-
In oefening 4 gebruik je een callback om de bevestigingscode op te vangen. Waarom is dit beter dan een publieke property
ConfirmationCodetoevoegen aan de klasse? -
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 doet | Wat je ziet als je code klopt |
|---|---|
| Vul je mandje en ga naar Bestellen | Het bedrag komt uit CartService.Total, niet meer uit een los product |
| Reken een leeg mandje af | Mandje is leeg uit jouw CheckoutService.Checkout() |
| Reken een gevuld mandje af | Betaling geslaagd en een bevestigingscode in de vorm ORD-000001-3CED |
| Bestel nog eens | Een 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.