Heh, coś czuję, że już wiem, gdzie to zmierza. W sumie to ciekawe, że jest to forum programistyczne, ale w tematach ogólnych ludzie niemal nigdy nie pokażą kodu, za to bez problemu piszą, jakiego to oni kodu nie potrafią stworzyć.
Wymaganie jest proste: nie możesz użyć prawdziwego doWithOptionalBackupConnection, tylko musisz go wymockować. Napisałeś wcześniej, że to bankowo da się przetestować mockując Client, więc pokaż ten test i po sprawie.
Wspominałem już, że z takim wymaganiem testu jednostkowego nie napiszę. Z pisaniem testów odnosiłem się do normalnej sytuacji, w której mogę napisać test mockując zewnętrzne zależności. Kod wygląda wtedy tak:
Kopiuj
using System;
using Xunit;
using NSubstitute;
using FluentAssertions;
using System.Threading.Tasks;
namespace TestProject1
{
public class UnitTest1
{
[Fact]
public void SyncDoWork_WhenFirstClientFails_ThenBackupClientIsUsed()
{
var mainClient = Substitute.For<Client>();
mainClient.GetData(default).ReturnsForAnyArgs(_ => throw new InvalidOperationException());
var backupClient = Substitute.For<Client>();
backupClient.GetData(default).ReturnsForAnyArgs("dupa");
new SyncWorker().DoWork(mainClient, backupClient).Should().Be("dupadupadupa");
}
[Fact]
public void NonSyncDoWork_WhenFirstClientFails_ThenBackupClientIsUsed()
{
var mainClient = Substitute.For<NonSyncClient>();
mainClient.GetData(default).ReturnsForAnyArgs<Task<string>>(_ => throw new InvalidOperationException());
var backupClient = Substitute.For<NonSyncClient>();
backupClient.GetData(default).ReturnsForAnyArgs("dupa");
new NonSyncWorker().DoWork(mainClient, backupClient).Should().Be("dupadupadupa");
}
}
class Util
{
public TResult DoWithOptionalBackupConnection<Client, TResult>(Client mainClient, Client backupClient, Func<Client, TResult> func)
{
try
{
return func(mainClient);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return func(backupClient);
}
}
public interface Client
{
public string GetData(string parameter);
}
class SyncWorker
{
private readonly Util utilInstance = new();
public string DoWork(Client mainClient, Client backupClient)
{
string first = utilInstance.DoWithOptionalBackupConnection(mainClient, backupClient, client => client.GetData("First"));
string second = utilInstance.DoWithOptionalBackupConnection(mainClient, backupClient, client => client.GetData("Second"));
string third = utilInstance.DoWithOptionalBackupConnection(mainClient, backupClient, client => client.GetData("Third"));
return $"{first}{second}{third}";
}
}
public interface NonSyncClient
{
public Task<string> GetData(string parameter);
}
class NonSyncWorker
{
private readonly Util utilInstance = new();
public string DoWork(NonSyncClient mainClient, NonSyncClient backupClient)
{
Task<string> first = utilInstance.DoWithOptionalBackupConnection(mainClient, backupClient, client => client.GetData("First"));
Task<string> second = utilInstance.DoWithOptionalBackupConnection(mainClient, backupClient, client => client.GetData("Second"));
Task<string> third = utilInstance.DoWithOptionalBackupConnection(mainClient, backupClient, client => client.GetData("Third"));
return $"{first.Result}{second.Result}{third.Result}";
}
}
}
Po zmianie klienta, kompilator będzie wymagał zmiany z 27 linijki.
Przy założeniu, że muszę stosować jakieś 3rd party, które zawiera logikę wykonującą mój kod, ale nie mogę ani go użyć ani zamockować jego zależności i przetestować normalnie, to najpierw proszę ownerów tego kodu o jego poprawienie, a jeśli się nie da, to po prostu nie piszę testu jednostkowego, muszą mi wystarczyć integracyjne.
Przykład @maszrum niczego niestety nie dowodzi, bo tam jest mock ze skopiowaną logiką produkcyjną. W rzeczywistym świecie to przestaje sensownie działać zazwyczaj wcześniej niż później. A jeśli nawet później, to jest jeszcze gorzej, bo nikt nie pamięta co tam zostało odwalone, więc i naprawienie potrwa dłużej niż powinno.