backend-testing by exceptionless
Testing
2.4K Stars
513 Forks
Updated Jan 12, 2026, 04:05 AM
Why Use This
This skill provides specialized capabilities for exceptionless's codebase.
Use Cases
- Developing new features in the exceptionless repository
- Refactoring existing code to follow exceptionless standards
- Understanding and working with exceptionless's codebase structure
Skill Snapshot
Auto scan of skill assets. Informational only.
Valid SKILL.md
Checks against SKILL.md specification
Source & Community
Skill Stats
SKILL.md 201 Lines
Total Files 1
Total Size 0 B
License NOASSERTION
---
name: Backend Testing
description: |
Backend testing with xUnit, Foundatio.Xunit, integration tests with AppWebHostFactory,
FluentClient, ProxyTimeProvider for time manipulation, and test data builders.
Keywords: xUnit, Fact, Theory, integration tests, AppWebHostFactory, FluentClient,
ProxyTimeProvider, TimeProvider, Foundatio.Xunit, TestWithLoggingBase, test data builders
---
# Backend Testing
## Running Tests
```bash
# All tests
dotnet test
# By test name
dotnet test --filter "FullyQualifiedName~CanPostUserDescriptionAsync"
# By class name
dotnet test --filter "ClassName~EventControllerTests"
```
## Test Folder Structure
Tests mirror the source structure:
```text
tests/Exceptionless.Tests/
├── AppWebHostFactory.cs # WebApplicationFactory for integration tests
├── IntegrationTestsBase.cs # Base class for integration tests
├── TestWithServices.cs # Base class for unit tests with DI
├── Controllers/ # API controller tests
├── Jobs/ # Job tests
├── Repositories/ # Repository tests
├── Services/ # Service tests
├── Utility/ # Test data builders
│ ├── AppSendBuilder.cs # Fluent HTTP request builder
│ ├── DataBuilder.cs # Test data creation
│ ├── EventData.cs
│ ├── OrganizationData.cs
│ ├── ProjectData.cs
│ ├── ProxyTimeProvider.cs # Time manipulation
│ └── ...
└── Validation/ # Validator tests
```
## Integration Test Base Pattern
Inherit from `IntegrationTestsBase` which uses Foundatio.Xunit's `TestWithLoggingBase`:
```csharp
// From tests/Exceptionless.Tests/IntegrationTestsBase.cs
public abstract class IntegrationTestsBase : TestWithLoggingBase, IAsyncLifetime, IClassFixture<AppWebHostFactory>
{
protected readonly TestServer _server;
private readonly ProxyTimeProvider _timeProvider;
public IntegrationTestsBase(ITestOutputHelper output, AppWebHostFactory factory) : base(output)
{
_server = factory.Server;
_timeProvider = GetService<ProxyTimeProvider>();
}
protected TService GetService<TService>() where TService : notnull
=> ServiceProvider.GetRequiredService<TService>();
protected FluentClient CreateFluentClient()
{
var settings = GetService<JsonSerializerSettings>();
return new FluentClient(CreateHttpClient(), new NewtonsoftJsonSerializer(settings));
}
}
```
## Real Test Example
From [EventControllerTests.cs](tests/Exceptionless.Tests/Controllers/EventControllerTests.cs):
```csharp
public class EventControllerTests : IntegrationTestsBase
{
private readonly IEventRepository _eventRepository;
private readonly IQueue<EventPost> _eventQueue;
public EventControllerTests(ITestOutputHelper output, AppWebHostFactory factory) : base(output, factory)
{
_eventRepository = GetService<IEventRepository>();
_eventQueue = GetService<IQueue<EventPost>>();
}
[Fact]
public async Task CanPostUserDescriptionAsync()
{
const string json = "{\"message\":\"test\",\"reference_id\":\"TestReferenceId\"}";
await SendRequestAsync(r => r
.Post()
.AsTestOrganizationClientUser()
.AppendPath("events")
.Content(json, "application/json")
.StatusCodeShouldBeAccepted()
);
var stats = await _eventQueue.GetQueueStatsAsync();
Assert.Equal(1, stats.Enqueued);
var processEventsJob = GetService<EventPostsJob>();
await processEventsJob.RunAsync();
await RefreshDataAsync();
var events = await _eventRepository.GetAllAsync();
var ev = events.Documents.Single(e => e.Type == Event.KnownTypes.Log);
Assert.Equal("test", ev.Message);
}
}
```
## FluentClient Pattern
Use `SendRequestAsync` with `AppSendBuilder` for HTTP testing:
```csharp
await SendRequestAsync(r => r
.Post()
.AsTestOrganizationUser() // Basic auth with test user
.AppendPath("organizations")
.Content(new NewOrganization { Name = "Test" })
.StatusCodeShouldBeCreated()
);
// Available auth helpers
r.AsGlobalAdminUser() // TEST_USER_EMAIL
r.AsTestOrganizationUser() // TEST_ORG_USER_EMAIL
r.AsFreeOrganizationUser() // FREE_USER_EMAIL
r.AsTestOrganizationClientUser() // API key bearer token
```
## Test Data Builders
Create test data with `CreateDataAsync`:
```csharp
var (stacks, events) = await CreateDataAsync(b => b
.Event()
.TestProject()
.Type(Event.KnownTypes.Error)
.Message("Test error"));
Assert.Single(stacks);
Assert.Single(events);
```
## Time Manipulation with ProxyTimeProvider
**NOT `ISystemClock`** — use .NET 8+ `TimeProvider` with `ProxyTimeProvider`:
```csharp
// Access via protected property
protected ProxyTimeProvider TimeProvider => _timeProvider;
// Advance time
TimeProvider.Advance(TimeSpan.FromHours(1));
// Set specific time
TimeProvider.SetUtcNow(new DateTimeOffset(2024, 1, 15, 12, 0, 0, TimeSpan.Zero));
// Restore to system time
TimeProvider.Restore();
```
Registered in test services:
```csharp
services.ReplaceSingleton<TimeProvider>(_ => new ProxyTimeProvider());
```
## Test Principles
- **Use real serializer** — Tests use the same JSON serializer as production
- **Use real time provider** — Manipulate via `ProxyTimeProvider` when needed
- **Refresh after writes** — Call `RefreshDataAsync()` after database changes
- **Clean state** — `ResetDataAsync()` clears data between tests
## Foundatio.Xunit
Base class provides logging integration:
```csharp
using Foundatio.Xunit;
public class MyTests : TestWithLoggingBase
{
public MyTests(ITestOutputHelper output) : base(output)
{
Log.DefaultLogLevel = LogLevel.Information;
}
}
```
Name Size