Unit testy w Infrastructure

Unit testy w Infrastructure .NET – jak testować File.ReadAllText zgodnie z Clean Architecture

Unit testy w Infrastructure .NET – jak testować File.ReadAllText zgodnie z Clean Architecture

Dlaczego Infrastructure psuje testy?

Testowanie warstwy Infrastructure w .NET to temat, który regularnie wywołuje frustrację. Domain? Czysta logika. Application? Use case’y i mocki. Ale gdy pojawiają się pliki, JSON i File.ReadAllText, większość projektów kapituluje.

Efekt?

  • testy zależne od środowiska,
  • brak izolacji,
  • tzw. unit testy, które w rzeczywistości są testami integracyjnymi.

W tym artykule pokażę Ci jak pisać prawdziwe unit testy w Infrastructure, bez kombinowania i bez łamania zasad Clean Architecture.

Problem: File.ReadAllText niszczy izolację testów

Dlaczego File.ReadAllText to problem testowy?

File.ReadAllText:

  • jest statycznym API,
  • ma twardą zależność od systemu plików,
  • nie da się go mockować.
var json = File.ReadAllText(path);

To oznacza, że:

  • test wymaga prawdziwego pliku,
  • wynik zależy od środowiska,
  • test przestaje być deterministyczny.

➡️ To nie jest unit test. To test integracyjny z systemem plików.

Clean Architecture a Infrastructure – gdzie jest granica?

W Clean Architecture obowiązuje prosta zasada:

  • Domain i Application nie wiedzą nic o szczegółach technicznych
  • Infrastructure zna szczegóły, ale nadal zależy od abstrakcji

Nie walczymy z File.ReadAllText() Nie piszemy własnego file systemu.

👉 Owijamy zależność w interfejs.

To jest dokładnie ta granica, o którą chodzi w dobrej architekturze.


Krok 1: Minimalna abstrakcja – IFileReader

Tworzymy najprostszą możliwą abstrakcję:

public interface IFileReader
{
    string ReadAllText(string path);
}

✔ jedna metoda
✔ zero logiki
✔ jednoznaczna odpowiedzialność

To nie jest overengineering. To punkt odcięcia zależności.


Krok 2: Implementacja produkcyjna w Infrastructure

public class FileReader : IFileReader
{
    public string ReadAllText(string path)=> File.ReadAllText(path);
}

📌 To jedyne miejsce w całej aplikacji, które zna system plików.


Krok 3: Refaktoryzacja repozytorium

Repozytorium przestaje znać File.

public class JsonPostacRepository : IPostacRepository
{
    private readonly IPostacFactory _postacFactory;
    private readonly IFileReader _fileReader;

    public JsonPostacRepository(IPostacFactory postacFactory, IFileReader fileReader)
    {
        _postacFactory = postacFactory;
        _fileReader = fileReader;
    }

    public List<Postac> PobierzPostacie(string sciezka)
    {
        var json = _fileReader.ReadAllText(sciezka);
        var configList = JsonSerializer.Deserialize<List<PostacDto>>(json);

        var lista = new List<Postac>();

        foreach (var cfg in configList)
        {
            if (cfg.Kategoria.ToLower() == "bohater")
                lista.Add(_postacFactory.StworzBohatera(cfg.Typ, cfg.Imie));
            else if (cfg.Kategoria.ToLower() == "potwor")
                lista.Add(_postacFactory.StworzPotwora(cfg.Typ));
        }
        return lista;
    }
}

Co zyskaliśmy?

  • repozytorium nie zna systemu plików
  • zależy wyłącznie od interfejsów
  • Infrastructure stała się testowalna

Krok 4: Rejestracja w Dependency Injection

services.AddScoped<IFileReader, FileReader>();

Produkcja → prawdziwy plik
Testy → mock


Krok 5: Unit test Infrastructure (bez plików)

public class JsonPostacRepositoryTests
{
    [Fact]
    public void PobierzPostacie_WithValidJson_ShouldReturnCharacters()
    {
        // Arrange
        var json = """
        [
            { "Kategoria": "Bohater", "Typ": "Wojownik", "Imie": "Aragorn" },
            { "Kategoria": "Potwor", "Typ": "Ork" }
        ]
        """;


        var fileRedearMock = new Mock<IFileReader>();
        fileRedearMock.Setup(r => r.ReadAllText("path"))
            .Returns(json);

        var aragon = new Wojownik("Aragorn");
        var ork = new Ork();

        var factoryMock = new Mock<IPostacFactory>();
        factoryMock.Setup(f => f.StworzBohatera("Wojownik", "Aragorn")).Returns(aragon);
        factoryMock.Setup(f => f.StworzPotwora("Ork")).Returns(ork);

        var repository = new JsonPostacRepository(
            factoryMock.Object,
            fileRedearMock.Object);

        // Act
        var result = repository.PobierzPostacie("path");

        // Assert
        result.Should().HaveCount(2);
        result.Should().Contain(aragon);
        result.Should().Contain(ork);
    }
}

Co testujemy naprawdę?

✔ logikę repozytorium
✔ mapowanie JSON → domena
✔ współpracę zależności

❌ system plików
❌ środowisko

To jest czysty unit test.


Checklist: kiedy stosować wrappery?

  • ⛔ statyczne API
  • ⛔ zależność od I/O
  • ⛔ trudność w mockowaniu

➡️ owijaj w interfejs


Co dalej?

  • testy błędnego JSON-a
  • obsługa wyjątków
  • walidacja danych
  • osobne testy integracyjne

Zobacz też:

👉 Zostaw komentarz – z czym masz największy problem przy testach?
👉 Sprawdź też: Unit Testing Core Layer w .NET
👉 Sprawdź też: Unit Testing Application Layer w .NET

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *