NSubstitute - łatwiejsze mockowanie
Mamy daną klasę Calculator
, która umożliwia operację dodawania Add()
. W trakcie dodawania aktualny stan akumulatora jest drukowany za pomocą interfejsu IPrinter
.
public interface IPrinter
{
void Print(string text);
}
public class ScreenPrinter : IPrinter
{
public void Print(string text)
{
Console.WriteLine(text);
}
}
public class Calculator
{
private IPrinter _printer;
public int Accumulator;
public Calculator(int accumulatorInitial, IPrinter printer)
{
Accumulator = accumulatorInitial;
_printer = printer;
}
public void Add(int val)
{
Accumulator += val;
_printer.Print($"Accumulator = {Accumulator}");
}
}
Poniżej przedstawiam 3 sposoby na przetestowanie działania metody Calculator.Add()
.
Tego testu moim zdaniem nie można nazwać UnitTest, gdyż wywołuje on także metodę z innej klasy. Dodatkowo ta metoda robi jakieś akcje, które są niepożądane. W tym wypadku jest to niegroźne Console.WriteLine(), ale w innych mogłaby wywoływać MessageBox, który całkowicie sparaliżuje test.
[Test]
public void CalculatorAdd_NotUnit_Test()
{
// Arrange
var printer = new ScreenPrinter();
var calc = new Calculator(1, printer);
// Act
calc.Add(4); // ! Testing also method of another class (ScreenPrinter.Print)
// Assert
calc.Accumulator.Should().Be(5);
}
Najlepiej do tego typu testów użyć jakiejś biblioteki mockującej. Tutaj została użyta NSubstitute. Z jej pomocą możemy przygotować interfejs IPrinter
tak, aby wykonywał dokładnie takie akcje jakie są potrzebne do przetestowania Add(). W tym przypadku nie trzeba nic definiować. Natomiast po wywołaniu Add() możemy sprawdzić, czy metoda Print() została wywołana, z jakimi argumentami, ile razy oraz czy wywołanie pasuje do jakiegoś wzorca.
using NSubstitute;
[Test]
public void CalculatorAdd_NSubstitute_UnitTest()
{
// Arrange
var printer = Substitute.For<IPrinter>();
var calc = new Calculator(1, printer);
// Act
calc.Add(4);
// Assert
calc.Accumulator.Should().Be(5);
printer.Received().Print("Accumulator = 5"); // Assert Print call (1)
printer.Received(1).Print(Arg.Any<string>()); // Assert Print call (2)
}
Trzecim sposobem przetestowania, który można zaliczyć do UnitTestu jest przygotowanie dodatkowej klasy IPrinterMock implementującej odpowiednie mechanizmy sprawdzania. Dla porównania zaimplementowałem dokładnie taki sam test jak wyżej.
class IPrinterMock : IPrinter
{
public string printText;
public int printCallCounter;
public void Print(string text)
{
printText = text;
printCallCounter++;
}
}
[Test]
public void CalculatorAdd_Mock_UnitTest()
{
// Arrange
var printer = new IPrinterMock();
var calc = new Calculator(1, printer);
// Act
calc.Add(4);
// Assert
calc.Accumulator.Should().Be(5);
printer.printCallCounter.Should().NotBe(0); // Assert Print call (1)
printer.printText.Should().Be("Accumulator = 5"); //
printer.printCallCounter.Should().Be(1); // Assert Print call (2)
}