> For the complete documentation index, see [llms.txt](https://docs.skail.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.skail.dev/construir/testar-depurar-e-observar/como-testar-funcoes-com-skail.platform.runtime.testing.md).

# Como testar funções com Skail.Platform.Runtime.Testing

Como escrever testes unitários de uma function e seus commands sem conectar ao skail: o pacote de testes simula o skail em memória, executa a function e deixa você inspecionar o que foi emitido e gravado.

## Pré-requisitos

No projeto de testes:

```xml
<PackageReference Include="Skail.Platform.Runtime.Testing" Version="2.2.*" />
<PackageReference Include="xunit" Version="2.5.3" />
```

Referência ao projeto que contém as functions.

## A function sob teste

```csharp
public class FaturamentoService
{
    private readonly IGatewayPagamento _gateway;
    private readonly IRepositorioFaturas _faturas;

    public FaturamentoService(IGatewayPagamento gateway, IRepositorioFaturas faturas)
    {
        _gateway = gateway;
        _faturas = faturas;
    }

    [SkailFunction]
    public async SkailTask EmitirFatura(Guid faturaId)
    {
        var valor = await CarregarValor(faturaId);
        var resultado = await CobrarCartao(faturaId, valor);
        if (resultado == ResultadoCobranca.Aprovado) await MarcarComoPaga(faturaId);
    }

    [SkailCommand]
    public async SkailTask<decimal> CarregarValor(Guid faturaId) => await _faturas.ObterValorAsync(faturaId);

    [SkailCommand(retryCount: 8)]
    public async SkailTask<ResultadoCobranca> CobrarCartao(Guid faturaId, decimal valor) => await _gateway.CobrarAsync(faturaId, valor);

    [SkailCommand]
    public async SkailTask MarcarComoPaga(Guid faturaId) => await _faturas.MarcarPagaAsync(faturaId);
}
```

As dependências entram pelo construtor como interfaces, para que o teste substitua por dublês. Essa é a regra de desenho que torna functions testáveis: a function decide, os commands falam com o mundo por interfaces.

## O teste

```csharp
using Skail.Platform.Runtime.Testing;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

public class EmitirFaturaTests
{
    [Fact]
    public async Task Cobranca_aprovada_marca_fatura_como_paga()
    {
        // Arrange
        var platform = UnitTestSkailPlatform.Create();
        var broker   = platform.ServiceProvider.GetRequiredService<UnitTestMessageBroker>();
        var ids      = platform.ServiceProvider.GetRequiredService<ITaskIdProvider>();

        // dublês das dependências
        var gateway = new GatewayFake(resultado: ResultadoCobranca.Aprovado);
        var faturas = new RepositorioFaturasFake(valor: 149.90m);

        var faturaId = Guid.NewGuid();
        var trigger = EventFrameHelper.CreateTrigger<FaturamentoService>(s => s.EmitirFatura(faturaId), ids);

        await broker.Produce(trigger, new Grpc.Core.Metadata(), new MessageId());

        // Act
        await platform.SimulateConsumeAndWaitTaskCompletion();

        // Assert: o efeito aconteceu
        Assert.True(faturas.FoiMarcadaComoPaga(faturaId));

        // Assert: o que o runtime emitiu
        var comandos = broker.Commands.ToList();
        Assert.Contains(comandos, c => c.Kind == MessageBrokerCommandKind.SaveState);

        // Assert: os passos registrados
        var estados = broker.GetAllStates(trigger.TargetTaskId);
        Assert.Equal(EventType.Rpc, estados[0].EventType);       // início da function
        Assert.True(estados.Count >= 4);                          // início + três commands
    }

    [Fact]
    public async Task Cobranca_recusada_nao_marca_como_paga()
    {
        var platform = UnitTestSkailPlatform.Create();
        var broker   = platform.ServiceProvider.GetRequiredService<UnitTestMessageBroker>();
        var ids      = platform.ServiceProvider.GetRequiredService<ITaskIdProvider>();
        var gateway  = new GatewayFake(resultado: ResultadoCobranca.Recusado);
        var faturas  = new RepositorioFaturasFake(valor: 149.90m);

        var faturaId = Guid.NewGuid();
        var trigger = EventFrameHelper.CreateTrigger<FaturamentoService>(s => s.EmitirFatura(faturaId), ids);
        await broker.Produce(trigger, new Grpc.Core.Metadata(), new MessageId());

        await platform.SimulateConsumeAndWaitTaskCompletion();

        Assert.False(faturas.FoiMarcadaComoPaga(faturaId));
    }
}
```

O que cada parte faz. `UnitTestSkailPlatform.Create()` monta um runtime em memória, sem conexão com o skail. `EventFrameHelper.CreateTrigger<T>(...)` cria a mensagem de início da function, do mesmo jeito que um trigger real. `broker.Produce(...)` entrega a mensagem ao runtime de teste. `SimulateConsumeAndWaitTaskCompletion()` consome, executa a function e todos os commands, e espera terminar. Depois, `broker.Commands` tem cada interação do runtime com a plataforma de teste (Produce, SaveState, Consume, Ack, Nak) e `broker.GetAllStates(taskId)` tem os passos registrados, na ordem.

## O que dá e o que não dá para testar

Dá: o caminho da function (quais commands rodaram, em que ordem, com que argumentos), o efeito nos dublês, os passos registrados, e o comportamento sob falha injetada em um command com `FaultInjection`.

Não dá, em unit test: hibernação. Uma function que alcança `Delay` ou `WaitForEvent` não satisfeito lança "Hibernation is not allowed in the unit test.". Para esses fluxos, teste até o ponto de espera e extraia a lógica de decisão para métodos puros, testáveis sem a plataforma; a espera em si fica para um teste de integração contra um environment de desenvolvimento.

Teste os commands como código comum: são métodos que chamam interfaces; um teste direto com o dublê basta, sem a plataforma de testes.

## Erros comuns

| Sintoma                                            | Causa                                                                     | Correção                                                                      |
| -------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| "Hibernation is not allowed in the unit test."     | A function chegou a um `Delay`/`WaitForEvent`                             | Teste até o ponto de espera, ou em integração                                 |
| "The Builder Factory can only be registered once!" | Dois `UnitTestSkailPlatform.Create()` no mesmo processo sem isolamento    | Uma plataforma por processo de teste; compartilhe a instância entre os testes |
| Function não é encontrada                          | Assembly de teste ou de produção sem `[assembly: VisibleToSkailPlatform]` | Adicione o atributo                                                           |

## Próximos passos

[Como depurar com Time Travel Debug](/construir/testar-depurar-e-observar/como-depurar-com-time-travel-debug.md) para reproduzir uma execução real. [Modelo de programação](/aprender/fundamentos/modelo-de-programacao.md) para a divisão que torna a function testável. [Pacotes](/construir/sdk-.net/pacotes.md).
