# Service tests — `{E}Service` (EF access) + infrastructure services

The `{E}Service` injects **`IExtensionsDbContext`** and queries the `extensions` schema.
Test it against a **real in-memory `ExtensionsDbContext`** built with a
`FakeCurrentTenantService` — NOT a mocked DbContext. EF Core InMemory **honours query
filters** (so the tenant filter runs), but does NOT enforce relational constraints, SQL
translation, or SqlObjects/TVF — those are the integration tests' job.

Tests go to `Tests/{Module}/Application/{Entity}ServiceTests.cs`.

## Canonical skeleton — entity service (in-memory ExtensionsDbContext)

```csharp
using Xunit;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using MyApp.Infrastructure.Persistence;     // ExtensionsDbContext
using MyApp.Infrastructure.Services.Orders;  // OrderService
using MyApp.Application.Orders.Commands;
using MyApp.Domain.Entities;
using MyApp.Tests.Common;                     // FakeCurrentTenantService

namespace MyApp.Tests.Orders.Application;

[Trait("Category", "Business")]
[Trait("Type", "Unit")]
public class OrderServiceTests
{
    private static OrderService CreateService(out ExtensionsDbContext context)
    {
        var options = new DbContextOptionsBuilder<ExtensionsDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        context = new ExtensionsDbContext(options, new FakeCurrentTenantService(Guid.NewGuid()));
        return new OrderService(context);
    }

    [Fact]
    public async Task CreateAsync_ValidCommand_PersistsEntity()
    {
        var service = CreateService(out var context);

        var id = await service.CreateAsync(new CreateOrderCommand("ORD-001", 100m),
            TestContext.Current.CancellationToken);

        id.Should().NotBeEmpty();
        (await context.Set<Order>().FindAsync(new object[] { id }, TestContext.Current.CancellationToken))
            .Should().NotBeNull();
    }
}
```

`FakeCurrentTenantService` is emitted once into `Tests/Common/`. For Core entities outside the
V1 whitelist, the service injects `ICoreDataService` — mock it (`Mock<ICoreDataService>`) and
set up the projection helpers (`GetUserBasicInfoAsync`, …).

## Infrastructure services (JWT, storage, cache, email, …)

- Dependencies as `Mock<T>` class fields; SUT built in a `private XyzService CreateService()`.
- **`Options.Create(new TOptions{…})`** — never `Mock<IOptions<T>>`. Config via
  `new ConfigurationBuilder().AddInMemoryCollection(dict).Build()`.
- **`NullLogger<T>.Instance`** unless you assert a log (then `Mock<ILogger<T>>` + `Verify`).
- Crypto/IO real where cheap (`RSA.Create`, temp dir in ctor + `IDisposable` cleanup).
- Deterministic time: a nested `FixedTimeProvider : TimeProvider`; for services calling
  `DateTime.UtcNow` directly, tolerate with `BeCloseTo(expected, TimeSpan.FromSeconds(10))`.
- Verify side effects: `_mock.Verify(c => c.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once)`.

## Anti-patterns

- ❌ `Mock<IExtensionsDbContext>` with hand-wired DbSets for query tests — use the in-memory
  `ExtensionsDbContext`. (Bare `Mock<IExtensionsDbContext>` is only for a service method that
  never queries — then just `Verify`.)
- ❌ `Mock<IOptions<T>>` / `Mock<ILogger<T>>` (without asserting logs) — `Options.Create` / `NullLogger<T>.Instance`.
- ❌ Instantiating the SUT inline in every `[Fact]` — use a `CreateService()` factory.
- ❌ `CancellationToken.None` — `TestContext.Current.CancellationToken`.
