NaturalApi turns your API tests into sentences you can read aloud. No boilerplate. No ceremony. Just clarity.
Because this...
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/users/1");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
var user = JsonSerializer.Deserialize<User>(content);
Assert.AreEqual(200, (int)response.StatusCode);
Assert.IsNotNull(user);…is the kind of code people write once, copy forever, and never want to look at again.
Now read this:
var user = await Api.For("/users/1")
.UsingAuth("Bearer token")
.Get()
.ShouldReturn<User>();That’s not just cleaner, it’s readable. It says exactly what it does. And when you come back to it six months later, you’ll still know what it means.
Lots of libraries make the first test look neat. NaturalApi keeps your hundredth one readable.
- No
HttpClientFactoryplumbing - No hidden
.Build()calls - No “where did this token come from” moments Just straight, expressive flow.
Every test reads like this:
Api.For(endpoint)
.WithHeaders(...) // Optional
.WithQueryParams(...) // Optional
.UsingAuth(...) // Optional
.<HttpVerb>(body) // GET, POST, PUT, etc.
.ShouldReturn<T>(...) // Validate response
Or in practice:
await Api.For("/orders/123")
.UsingAuth("Bearer token")
.Get()
.ShouldReturn<Order>(status: 200, body: o => o.Total > 0);You can actually read that aloud. And it still compiles.
// 1. Simple GET
await Api.For("/users").Get().ShouldReturn<List<User>>();
// 2. POST with validation
await Api.For("/users").Post(newUser).ShouldReturn<User>(status: 201);
// 3. Authenticated request
await Api.For("/protected").UsingAuth("Bearer token").Get();
// 4. Query parameters
await Api.For("/search").WithQueryParam("q", "api testing").Get();
// 5. Delete with assertion
await Api.For("/users/1").Delete().ShouldReturn(204);Readable, predictable, and type-safe. Because testing APIs shouldn’t feel like writing networking code.
dotnet add package NaturalApiThe ShouldReturn method handles both verification and clarity:
// Type only
.ShouldReturn<User>()
// Status code
.ShouldReturn(status: 201)
// Type and status
.ShouldReturn<User>(status: 200)
// Validate body
.ShouldReturn<User>(body: u => u.Name == "John")
// All at once
.ShouldReturn<User>(
status: 201,
body: u => u.Id > 0 && !string.IsNullOrEmpty(u.Email),
headers: h => h.ContainsKey("Location")
);Every validation reads like what you’d say aloud:
“It should return a user, with status 201, whose email isn’t empty.”
NaturalApi was designed for DI- not bolted onto it.
// Program.cs or Startup.cs
services.AddNaturalApi(options =>
{
options.BaseUrl = "https://api.example.com";
options.Timeout = TimeSpan.FromSeconds(30);
options.DefaultHeaders = new Dictionary<string, string>
{
["Accept"] = "application/json"
};
});
services.AddSingleton<IApiAuthProvider, MyAuthProvider>();Then in your code:
public class UserController
{
private readonly IApi _api;
public UserController(IApi api)
{
_api = api;
}
public async Task<User> GetUser(int id)
{
return await _api.For($"/users/{id}")
.Get()
.ShouldReturn<User>();
}
}No client factories. No setup churn. Just inject and use.
[TestClass]
public class UserServiceTests
{
private MockHttpExecutor _mockExecutor;
private IApi _api;
[TestInitialize]
public void Setup()
{
_mockExecutor = new MockHttpExecutor();
_api = new Api(_mockExecutor);
}
[TestMethod]
public async Task GetUser_Should_Return_User()
{
_mockExecutor.SetupResponse("/users/1", HttpStatusCode.OK, new { id = 1, name = "John" });
var user = await _api.For("/users/1")
.Get()
.ShouldReturn<User>();
Assert.AreEqual("John", user.Name);
}
}[TestClass]
public class UserApiIntegrationTests
{
[TestMethod]
public async Task Create_User_Should_Return_201()
{
var newUser = await Api.For("https://jsonplaceholder.typicode.com/users")
.WithHeaders(new Dictionary<string, string>
{
["Content-Type"] = "application/json"
})
.Post(new { name = "John Doe", email = "john@example.com" })
.ShouldReturn<User>(status: 201);
Assert.IsNotNull(newUser);
Assert.IsTrue(newUser.Id > 0);
}
}Mocks if you need speed. Live calls if you need confidence.
NaturalApi is built on four simple layers:
- Fluent DSL – what you write
- Context Builders – immutable state composition
- Execution Engine – HTTP and deserialisation
- Validation Layer – readable, declarative assertions
Each layer has one job. Nothing hidden, nothing magic, nothing you’ll regret later.
- Speak like a human – if you can’t say it, don’t write it
- Mirror tester logic –
For → With → Using → Do → ShouldReturn - Optional and predictable – no surprises, no “setup or die”
- Readable failures – assertions that explain themselves
- Fluent, not fragile – no builders, no boilerplate
I built NaturalApi because writing API tests shouldn’t feel like wiring up a web server. You shouldn’t need five lines of setup just to say “this worked.”
If your tests read like English and fail like humans speak, you’ll actually read them again.
- Getting Started - Installation, first API call, basic setup
- Configuration - Base URLs, timeouts, default headers, DI setup
- Examples - Real-world scenarios and complete examples
- Request Building - Headers, query params, path params, cookies
- HTTP Verbs - GET, POST, PUT, PATCH, DELETE with examples
- Assertions - ShouldReturn variations, validation patterns
- Authentication - Auth providers, caching, per-user tokens
- Error Handling - Exception types, debugging, troubleshooting
- Testing Guide - Unit testing with mocks, integration testing
- Extensibility - Custom executors, validators, auth providers
- Reporting - Configurable reporters, DI factory and examples
- API Reference - Complete interface and class documentation
- Troubleshooting - Common issues and solutions
- Contributing - Architecture internals and contribution guidelines
- Philosophy & Design Principles - Core design philosophy
- Fluent Syntax Reference - Complete grammar and method reference
- Dependency Injection Guide - DI patterns and ServiceCollectionExtensions
- Architecture Overview - Internal design and implementation
MIT. Do what you want, just don't ruin the readability.
Good ideas welcome. Over-engineering isn't.