Skip to content

Instantly share code, notes, and snippets.

@marbel82
Last active April 4, 2020 08:23
Show Gist options
  • Save marbel82/0d374e2d85104609da30cee62b407e0e to your computer and use it in GitHub Desktop.
Save marbel82/0d374e2d85104609da30cee62b407e0e to your computer and use it in GitHub Desktop.

NSubstitute - łatwiejsze mockowanie

0) Example code

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().

1) Without mock

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);
}

2) NSubstitute

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)
}

3) User mock

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)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment