Moq w C# — podstawy mockowania zależności
Klasa, którą chcesz przetestować, często zależy od innych klas — repozytorium bazy danych, klienta HTTP, wysyłki e-maili. Moq pozwala podstawić w ich miejsce “atrapę”, która zachowuje się dokładnie tak, jak jej każesz, bez uruchamiania prawdziwej bazy danych czy serwera SMTP.
Problem, który Moq rozwiązuje
public class RabatService
{
private readonly IKlientRepository _repozytorium;
public RabatService(IKlientRepository repozytorium) => _repozytorium = repozytorium;
public decimal ObliczRabat(int klientId)
{
var klient = _repozytorium.ZnajdzPoId(klientId);
return klient.LiczbaZamowien > 10 ? 0.15m : 0.05m;
}
}Żeby przetestować ObliczRabat, potrzebujesz IKlientRepository — ale nie chcesz w teście jednostkowym łączyć się z prawdziwą bazą danych. Moq tworzy fałszywą implementację interfejsu w locie.
Mock<T> i Setup — sterowanie zwracaną wartością
using Moq;
using Xunit;
[Fact]
public void ObliczRabat_KlientZDuzaLiczbaZamowien_Zwraca15Procent()
{
var mockRepo = new Mock<IKlientRepository>();
mockRepo.Setup(r => r.ZnajdzPoId(5))
.Returns(new Klient { Id = 5, LiczbaZamowien = 20 });
var serwis = new RabatService(mockRepo.Object);
var rabat = serwis.ObliczRabat(5);
Assert.Equal(0.15m, rabat);
}mockRepo.Object to fałszywy IKlientRepository, który zamiast pytać bazę danych, po prostu zwraca to, co ustawiłeś w Setup. RabatService nie wie (i nie musi wiedzieć), że dostał atrapę zamiast prawdziwego repozytorium — testujesz wyłącznie logikę ObliczRabat.
It.IsAny<T>() — dowolna wartość argumentu
mockRepo.Setup(r => r.ZnajdzPoId(It.IsAny<int>()))
.Returns(new Klient { Id = 999, LiczbaZamowien = 3 });It.IsAny<int>() mówi: “dla dowolnego int przekazanego do ZnajdzPoId, zwróć tego samego klienta”. Przydatne, gdy dokładna wartość argumentu nie ma znaczenia dla testowanego scenariusza.
Verify — sprawdzanie, czy metoda została wywołana
[Fact]
public void ZlozZamowienie_PoprawneDane_WysylaPotwierdzenieEmail()
{
var mockPowiadomienia = new Mock<IPowiadomienia>();
var serwis = new ZamowienieService(mockPowiadomienia.Object);
serwis.ZlozZamowienie(new Zamowienie { KlientEmail = "test@example.com" });
mockPowiadomienia.Verify(
p => p.WyslijAsync("test@example.com", It.IsAny<string>()),
Times.Once);
}Verify nie sprawdza wartości zwróconej — sprawdza, czy dana metoda w ogóle została wywołana, ile razy (Times.Once, Times.Never, Times.Exactly(3)) i z jakimi argumentami. To kluczowe dla metod, które nie zwracają wartości, tylko wywołują efekt uboczny (wysyłka e-maila, zapis do logu).
Throws — symulowanie błędu zależności
mockRepo.Setup(r => r.ZnajdzPoId(It.IsAny<int>()))
.Throws(new InvalidOperationException("Baza danych niedostępna"));
Assert.Throws<InvalidOperationException>(() => serwis.ObliczRabat(5));Symulowanie awarii zależności (baza danych padła, zewnętrzne API zwróciło błąd) bez faktycznego wyłączania czegokolwiek — sprawdzasz, czy Twój kod poprawnie reaguje na wyjątek.
MockBehavior.Strict — gdy każde wywołanie musi być jawnie skonfigurowane
var mockRepo = new Mock<IKlientRepository>(MockBehavior.Strict);
mockRepo.Setup(r => r.ZnajdzPoId(5)).Returns(new Klient { Id = 5 });
// wywołanie ZnajdzPoId(6) na tym mocku rzuci wyjątkiem — nieskonfigurowaneDomyślnie (MockBehavior.Loose) niekonfigurowane wywołania zwracają wartość domyślną (null, 0, false) zamiast rzucać błąd. MockBehavior.Strict wymusza jawną konfigurację każdego wywołania — pomaga wyłapać scenariusz, w którym kod wywołuje zależność w nieoczekiwany sposób.
Kiedy NIE mockować
Mockuj granice systemu — repozytoria, klientów HTTP, wysyłkę e-maili, zegar systemowy. Nie mockuj prostych obiektów wartości (Adres, Pieniądze) ani własnej logiki domenowej — jeśli mockujesz zbyt dużo, test przestaje sprawdzać cokolwiek realnego i tylko powtarza to, co sam skonfigurowałeś w Setup.
Zobacz praktyczne zastosowanie Moq w kontekście pełnego projektu w Unit Testing Application Layer w .NET — Mockowanie zależności z Moq, gdzie mockowanie jest częścią większej serii o Clean Architecture.
Podsumowanie
Moq pozwala testować klasę w izolacji od jej zależności: Mock<T> tworzy atrapę interfejsu, Setup/Returns kontroluje co zwraca, It.IsAny<T>() dopasowuje dowolny argument, Verify sprawdza czy metoda została wywołana, a Throws symuluje awarię. Klucz to mockować granice systemu (I/O, sieć, czas), nie własną logikę biznesową — inaczej test przestaje cokolwiek weryfikować.
🚀 Co dalej?
Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →
1 comment