Skip to content

Latest commit

 

History

History
94 lines (58 loc) · 5.05 KB

api-testing.md

File metadata and controls

94 lines (58 loc) · 5.05 KB

API testing

ApiTester (package UnitTestEx, plus your test framework package) wraps ASP.NET Core's WebApplicationFactory to host your API in-process. Requests go through the full ASP.NET Core pipeline: middleware, routing, model binding, filters, authorization and serialization. External dependencies are replaced through DI.

← Back to README


Create

using var test = ApiTester.Create<Startup>(); // Or ApiTester.Create<Program>() for minimal hosting.

Where the API's appsettings.json isn't next to the test assembly, use UseSolutionRelativeContentRoot("tests/My.Api"). Example: Success2.


Controller tests

Controller<TController>().Run(c => c.Method(args)) takes the method call, works out the HTTP method, route, query string and body from the controller's attributes, and sends a real HTTP request through TestServer. This gives refactor-safe, strongly-typed tests that still exercise the whole pipeline.

test.Controller<PersonController>()
    .Run(c => c.Get(1))
    .AssertOK()
    .AssertValue(new Person { Id = 1, FirstName = "Bob", LastName = "Smith" });

Example: Get_Test1, Get_Test4 (not found), Update_Test1, GetByArgs_Test1.


Raw HTTP tests

Http() sends an explicit request. Http<TResponse>() also gives the response a type, so ExpectValue and AssertValue are strongly typed.

test.Http()
    .Run(HttpMethod.Post, "Person/1", new Person { FirstName = "Bob", LastName = "Smith" })
    .AssertOK();

test.Http<Person>()
    .ExpectStatusCode(HttpStatusCode.OK)
    .ExpectValue(new Person { Id = 1, FirstName = "Bob", LastName = "Smith" })
    .Run(HttpMethod.Post, "Person/1", new Person { FirstName = "Bob", LastName = "Smith" });

Example: Http_Get1, Http_Update1, Http_Update_Http2, Http_Update_Http3_AsJson.


Errors and problem details

AssertErrors understands both ValidationProblemDetails and the simple { field: [messages] } error shape. ApiError also converts implicitly from a (field, message) tuple.

test.Http().Run(HttpMethod.Get, "product/test/problem").AssertErrors(new ApiError("id", "Not specified."));
test.Http<Person>().Run(HttpMethod.Post, "Person/88", p).Assert(HttpStatusCode.BadRequest, "No can do eighty-eight.");

Example: ValidationProblemDetails, Http_Update_Http5, Update_Test6.


IActionResult without HTTP

To check an IActionResult created outside the pipeline, wrap it in a ValueAssertor. Then call ToActionResultAssertor(), or ToHttpResponseMessageAssertor(httpRequest) to run the result's ExecuteResultAsync and assert the HTTP response it produces. Example: Type_IActionResult.


Security

Method Description
UseUser(userName) / UseUser(object) Sets the test user name for all requests; it defaults to TestSetUp.DefaultUserName ("Anonymous"). UnitTestEx doesn't create claims or tokens itself. The user name is passed to TestSetUp.OnBeforeHttpRequestMessageSendAsync, where you add whatever your API's authentication expects, such as a bearer token or header.
Http().WithUser(...) / Controller<T>().WithUser(...) Sets the user for a single tester.
BypassAuthorization() Replaces IAuthorizationHandler with a handler that succeeds every requirement. Use it when a test isn't about authorization.

Cross-cutting run actions

AddPreRunAction, AddPostRunBeforeExpectationsAction, AddPostRunAfterExpectationsAction and AddPostRunAction register logic that runs for every Run on that tester. Use them for things like adding a standard expectation, without repeating it in each test.


Accessing the host

test.Services (the built IServiceProvider), test.Configuration, test.GetTestServer() and test.GetLogger<T>() give direct access for any arrangement or assertion the fluent API doesn't cover. Example: ServiceProvider, Configuration.

See also: Dependency injection and configuration, Assertions and expectations, HTTP client mocking.

← Back to README