Testy – biblioteka Moq

To, że testy trzeba pisać, jest oczywiste. Czasami testy nie są jednak zbyt dobrze napisane. Warto więc na nie spojrzeć i je poprawić.

Przykład serwisu

Załóżmy, że mam metodę w serwisie, która chce na podstawie id zwrócić obiekt typu GameTable. W metodzie tej nie dzieje się nic poza wywołaniem odpowiedniej metody z repozytorium:

<br>
public class GameTableService : IGameTableService<br>
{<br>
	public GameTable GetGameTable(int id)<br>
	{<br>
		return _gameTableRepository.Get(id);<br>
	}<br>
}<br>

Przykład repozytorium

Przykładowe repozytorium może wyglądać następująco.

<br>
public class GameTableRepository : IGameTableRepository<br>
{<br>
	private readonly List&lt;GameTable&gt; _gameTables = GameTableGenerator.GameTables;</p>
<p>	public GameTable Get(int id)<br>
	{<br>
		return _gameTables.FirstOrDefault(x =&gt; x.Id == id);<br>
	}<br>
}<br>

Oczywiście docelowo pobiera ono dane z bazy danych, ale na ten moment wystarczy statyczna lista obiektów typu GameTable.

Przykład niezbyt dobrego testu

Poniżej przedstawiam przykładowy test, który w teorii ma na celu sprawdzić poprawność metody GetGameTable(int gameTableId) z serwisu.

<br>
[TestMethod]<br>
public void GetGameTable()<br>
{<br>
	//Arrange<br>
	var gameTableService = new GameTableService(new GameTableRepository());<br>
	var newGameTableId = GameTableGenerator.gameTables.Max(x =&gt; x.Id) + 1;<br>
	//method to get new GameTable object with with a specific id<br>
	var newGameTable = GetTestGameTableWithId(newGameTableId);<br>
	//Act<br>
	gameTableService.CreateGameTable(newGameTable);<br>
	var gameTable = _gameTableService.GetGameTable(newGameTableId);<br>
	//Assert<br>
	Assert.AreEqual(newGameTableId, gameTable.Id);<br>
}<br>

Co jest nie tak w tym teście? Wiele rzeczy, m.in.:

  • tworzenie nowej instancji serwisu z wykorzystaniem prawdziwego repozytorium (czyli operowanie na prawdziwych danych),
  • wywołanie nie tylko sprawdzanej metody z serwisu GetGameTable(), ale również metody CreateGameTable(),
  • Assert sprawdza tak na prawdę działanie repozytorium – czy zwrócony przez repozytorium obiekt ma odpowiednie id,
  • pomijam tymczasowe rozwiązanie pobierania newGameTableId.

Przykład dobrego testu (z wykorzystaniem Moq)

Teraz warto się zastanowić, co na prawdę chcemy testować. Jeśli jest to serwis, to cała logika z metody z repozytorium nie jest nam potrzebna. Chcemy się tylko dowiedzieć, czy metoda z repozytorium została wywołana (i ewentualnie, czy argument był prawidłowy). Przejdźmy więc po kolei po każdej części testu:

  • Arrange – definicja danych wejściowych

Możemy stworzyć symulowany obiekt repozytorium:

<br>
Mock&lt;IGameTableRepository&gt; gameTableRepositoryMock = new Mock&lt;IGameTableRepository&gt;();<br>

I wykorzystać metodę Setup() z biblioteki Moq. Dzięki niej możemy określić, którą metodę chcemy zasymulować. W naszym przypadku będzie to metoda Get(int id) z BoardGameRepository.

<br>
gameTableRepositoryMock.Setup(mock =&gt; mock.Get(It.IsAny&lt;int&gt;()));<br>

Jeśli chcielibyśmy dodatkowo mieć pewność, że ta metoda zwróci odpowiedni obiekt, możemy dodać metodę Returns() ze zwracanym obiektem:

<br>
var testGameTable = new GameTable();<br>
gameTableRepositoryMock.Setup(mock =&gt; mock.Get(It.IsAny&lt;in&gt;())).Returns(testGameTable);<br>

Gdy zasymulowany obiekt repozytorium jest gotowy, można go wykorzystać przy tworzeniu obiektu serwisu:

<br>
var gameTableService = new GameTableService(gameTableRepositoryMock.Object);<br>

  • Act – działanie, które chcemy sprawdzić

Za każdym razem gdy wywołamy metodę GetGameTable(int gameTableId) serwisu, wywoła ona symulację metody Get() repozytorium.

<br>
//Act<br>
var gameTable = gameTableService.GetGameTable(id);<br>

  • Assert – sprawdzenie, czy wynik jest zgodny z oczekiwanym

Ok, to jak teraz sprawdzić, czy działa? Czy podczas wywołania testu została wywołana metoda Get() repozytorium z daną wartością parametru id? Do tego przyda się metoda Verify(), w której wskazujemy sprawdzaną metodę wraz z parametrami oraz możemy określić ile razy miała się wywołać. W naszym przypadku powinna wywołać się raz, stąd parametr Times.Once().

<br>
//Assert<br>
gameTableRepositoryMock.Verify(mock =&gt; mock.Get(It.Is&lt;int&gt;(x =&gt; x.Equals(id))), Times.Once());<br>

Skoro przeszliśmy po kolei przez wszystkie etapy testu, to na koniec można przeanalizować cały test:

<br>
[TestMethod]<br>
public void GetGameTable()<br>
{<br>
	//Arrange<br>
	int id = 1;<br>
	Mock&lt;IGameTableRepository&gt; gameTableRepositoryMock = new Mock&lt;IGameTableRepository&gt;();<br>
	gameTableRepositoryMock.Setup(mock =&amp;amp;amp;amp;amp;gt; mock.Get(It.IsAny&lt;int&gt;()));<br>
	var gameTableService = new GameTableService(gameTableRepositoryMock.Object);<br>
	//Act<br>
	var gameTable = gameTableService.GetGameTable(id);<br>
	//Assert<br>
	gameTableRepositoryMock.Verify(mock =&gt; mock.Get(It.Is&lt;int&gt;(x =&gt; x.Equals(id))), Times.Once());<br>
}<br>

Dokumentację do biliboteki Moq możecie znaleźć tutaj.


Podoba Ci się to, co tworzę? Chcesz dostawać informacje o:
– wydarzeniach, które organizuję lub wspieram (np. konferencje, meetupy, webinary)
– inicjatywach, które organizuję lub wspieram (np. GeekWeekWro, DevAdventCalendar)
– moich prelekcjach, kursach i szkoleniach
– wyróżnionych artykułach z mojego bloga

0% SPAMu, 100% informacji! Krótko i na temat.

Jedna uwaga do wpisu “Testy – biblioteka Moq

  1. Marcin

    Moq wg mnie nie jest dobrą biblioteką, gdyż zaciemnia granice pomiędzy mock’ami i stub’ami. Chodzi o użycie słowa Mock. Poza tym same setupy są dość długie. Póki co najlepszym frameworkiem dla mnie jest NSubstitute, w którym składnia jest zwięzła i czytelna.

    Polubione przez 1 osoba

Dodaj komentarz