Xunit Support Classes - denmitchell/EDennis.AspNetCore.Base GitHub Wiki
EDennis.AspNetCore.Base provides a number of abstract Xunit test classes and supporting classes for performing various kinds of Xunit integration tests. There are four broad categories of integration tests supported: (1) Repo Tests, which directly test repo class methods; (2) Controller Tests, which directly test controller methods (actions); (3) ApiClient Tests, which directly test ApiClient methods; and (4) Endpoint Tests, which test an API endpoint. Only the Endpoint Tests make use of the Microsoft's WebApplicationFactory. All other tests use special factory classes that do not derive from WebApplicationFactory.
Four abstract classes provide support for testing repo methods. There is one abstract class for each of the four types of repos.
This class instantiates a ReadonlyRepo for use in directly testing the repo's public methods. The instantiated repo is exposed as a property called Repo.
ReadonlyRepoTests has three type parameters.
| Type Parameter | Constraints |
|---|---|
TRepo |
The type of the instantiated repo, which must extend ReadonlyRepo<TEntity,TContext> |
TEntity |
TEntity is a class with a default constructor |
TContext |
TContext extends Microsoft.EntityFrameworkCore.DbContext |
ReadonlyRepoTests has a single constructor with two arguments. The arguments for the constructor are injected into the test by the unit testing runtime.
| Parameter | Description |
|---|---|
ITestOutputHelper output |
The Xunit helper class for piping messages to the test output console |
ConfigurationFactory<TRepo> fixture |
A class for finding the applicable appsettings.Development.json file and generating an IConfiguration from that file |
ReadonlyRepoTests exposes two public properties.
| Property | Description |
|---|---|
public TRepo Repo |
Provides direct access to the Repo object |
public ITestOutputHelper Output |
The Xunit.Abstractions helper object for writing to the test output console |
public class TodoRepoTests : ReadonlyRepoTests<
TodoRepo, Todo, TodoContext> {
public TodoRepoTests(ITestOutputHelper output,
ConfigurationFactory<TodoRepo> factory)
: base(output, factory) { }
// test methods...
}StateAgencyBackgroundCheckRepoTests
This class instantiates a ReadonlyTemporalRepo for use in directly testing the repo's public methods. The instantiated repo is exposed as a property called Repo.
ReadonlyTemporalRepoTests has four type parameters.
| Type Parameter | Constraints |
|---|---|
TRepo |
The type of the instantiated repo, which must extend ReadonlyRepo<TEntity,TContext> |
TEntity |
TEntity is a class with a default constructor and implements IEFCoreTemporalModel |
TContext |
TContext extends Microsoft.EntityFrameworkCore.DbContext (for current records) |
THistoryContext |
THistoryContext extends Microsoft.EntityFrameworkCore.DbContext (for history records) |
ReadonlyTemporalRepoTests has a single constructor with two arguments. The arguments for the constructor are injected into the test by the unit testing runtime.
| Parameter | Description |
|---|---|
ITestOutputHelper output |
The Xunit helper class for piping messages to the test output console |
ConfigurationFactory<TRepo> factory |
A class for finding the applicable appsettings.Development.json file and generating an IConfiguration from that file |
ReadonlyTemporalRepoTests exposes two public properties.
| Property | Description |
|---|---|
public TRepo Repo |
Provides direct access to the Repo object |
public ITestOutputHelper Output |
The Xunit.Abstractions helper object for writing to the test output console |
public class TodoRepoTests : ReadonlyTemporalRepoTests<
TodoRepo, Todo, TodoContext, TodoHistoryContext> {
public TodoRepoTests(ITestOutputHelper output,
ConfigurationFactory<TodoRepo> factory)
: base(output, factory) { }
// test methods...
}FederalAgencyBackgroundCheckRepoTests
This class instantiates a WriteableRepo for use in directly testing the repo's public methods. The instantiated repo is exposed as a property called Repo.
WriteableRepoTests has three type parameters.
| Type Parameter | Constraints |
|---|---|
TRepo |
The type of the instantiated repo, which must extend WriteableRepo<TEntity,TContext> |
TEntity |
TEntity is a class with a default constructor |
TContext |
TContext extends Microsoft.EntityFrameworkCore.DbContext |
WriteableRepoTests has a single constructor with two required arguments and one optional argument. The required arguments for the constructor are injected into the test by the unit testing runtime.
| Parameter | Required? | Description |
|---|---|---|
ITestOutputHelper output |
Yes | The Xunit helper class for piping messages to the test output console |
ConfigurationFactory<TRepo> factory |
Yes | A class for finding the applicable appsettings.Development.json file and generating an IConfiguration from that file |
string userName |
No | The SysUser value to attached to the updated record (default = "[email protected]") |
WriteableRepoTests exposes three public properties.
| Property | Description |
|---|---|
public TRepo Repo |
Provides direct access to the Repo object |
public ITestOutputHelper Output |
The Xunit.Abstractions helper object for writing to the test output console |
public string InstanceName |
The generated name of the in-memory database (a GUID string) |
public class TodoRepoTests : WriteableRepoTests<
TodoRepo, Todo, TodoContext> {
public TodoRepoTests(ITestOutputHelper output,
ConfigurationFactory<TodoRepo> factory, "bob@example.org")
: base(output, factory) { }
// test methods...
}This class instantiates a WriteableTemporalRepo for use in directly testing the repo's public methods. The instantiated repo is exposed as a property called Repo.
WriteableTemporalRepoTests has four type parameters.
| Type Parameter | Constraints |
|---|---|
TRepo |
The type of the instantiated repo, which must extend WriteableTemporalRepo<TEntity,TContext> |
TEntity |
TEntity is a class with a default constructor |
TContext |
TContext extends Microsoft.EntityFrameworkCore.DbContext (for current records) |
THistoryContext |
THistoryContext extends Microsoft.EntityFrameworkCore.DbContext (for history records) |
WriteableTemporalRepoTests has a single constructor with two required arguments and one optional argument. The required arguments for the constructor are injected into the test by the unit testing runtime.
| Parameter | Required? | Description |
|---|---|---|
ITestOutputHelper output |
Yes | The Xunit helper class for piping messages to the test output console |
ConfigurationFactory<TRepo> factory |
Yes | A class for finding the applicable appsettings.Development.json file and generating an IConfiguration from that file |
string userName |
No | The SysUser value to attached to the updated record (default = "[email protected]") |
WriteableTemporalRepoTests exposes four public properties.
| Property | Description |
|---|---|
public TRepo Repo |
Provides direct access to the Repo object |
public ITestOutputHelper Output |
The Xunit.Abstractions helper object for writing to the test output console |
public string InstanceName |
The generated name of the in-memory database (current records) |
public string HistoryInstanceName |
The generated name of the in-memory database (history records) |
public class TodoRepoTests : WriteableTemporalRepoTests<
TodoRepo, Todo, TodoContext, TodoHistoryContext> {
public TodoRepoTests(ITestOutputHelper output,
ConfigurationFactory<TodoRepo> factory, "alice@example.org")
: base(output, factory) { }
// test methods...
}AgencyInvestigatorCheckRepoTests
EDennis.AspNetCore.Base does not provide any base test classes for testing controller methods directly. Instead, the library provides factory classes that make it easy to instantiate controllers with Repo or ApiClient dependencies in their constructors. The factory classes provide flexibility over base classes -- allowing the developer to inject any number and kind of Repo classes and any number and kind of ApiClient classes into a controller for testing.
The TestRepoFactory class provides static methods for instantiating each of the four kinds of repos (readonly, readonly-temporal, writeable, and writeable-temporal), as well as for retrieving the in-memory database name (instance name). This factory class can be used to test controllers that take one or more Repo objects as constructor parameters.
| Type Parameter | Constraints |
|---|---|
TRepo |
The type of the instantiated repo, which must extend WriteableTemporalRepo<TEntity,TContext> |
TEntity |
TEntity is a class with a default constructor |
TContext |
TContext extends Microsoft.EntityFrameworkCore.DbContext (for current records) |
THistory |
THistoryContext extends Microsoft.EntityFrameworkCore.DbContext (for history records) |
T |
T is any class in the project that holds the relevant appsettings.Development.json (can be TRepo) |
| Method |
|---|
public TRepo CreateReadonlyRepo<TRepo,TEntity,TContext,T>(ConfigurationFactory<T> factory) |
public TRepo CreateReadonlyTemporalRepo<TRepo,TEntity,TContext,THistoryContext, T>(ConfigurationFactory<T> factory) |
public TRepo CreateWriteableRepo<TRepo,TEntity,TContext,T>(ConfigurationFactory<T> factory, string testUser = DEFAULT_USER) |
public TRepo CreateReadonlyRepo<TRepo,TEntity,TContext,THistoryContext,T>(ConfigurationFactory<T> factory, string testUser = DEFAULT_USER) |
//...
//constructor...
public TodoControllerTests(ITestOutputHelper output,
ConfigurationFactory<TodoController> factory){
_output = output;
//create one or more repos using factory method
_repo = TestRepoFactory.CreateWriteableTemporalRepo<
TodoRepo, Todo, TodoDbContext, TodoHistoryDbContext, TodoController>(factory);
//construct a new controller, passing in the repo(s) and a logger
_ctlr = new TodoController(_repo, new Logger<TodoController>(new LoggerFactory()));
}
//test methods ...The TestApiClientFactory class provides static methods for instantiating ApiClients for writeable or readonly endpoints, as well as for retrieving the associated in-memory database name (instance name). This factory class can be used to test controllers that take one or more ApiClient objects as constructor parameters.
| Type Parameter | Constraints |
|---|---|
TClient |
The type of the instantiated ApiClient |
TStartup |
The Startup class of the target API to which the ApiClient points |
| Method |
|---|
public TRepo CreateReadonlyClient<TClient,TStartup>(ApiLauncherFactory<TStartup> factory) |
public TRepo CreateWriteableClient<TClient,TStartup>(ApiLauncherFactory<TStartup> factory, string testUser = DEFAULT_USER) |
using A = Namespace.For.TargetApi;
//...
//constructor...
public TodoExternalControllerTests(ITestOutputHelper output,
ApiLauncherFactory<A.Startup> factory){
_output = output;
//create one or more API clients using factory method
_api = TestRepoFactory.CreateWriteableClient<
TodoApiClient, A.Startup>(factory);
//construct a new controller, passing in the api client(s) and a logger
_ctlr = new TodoExternalController(_api, new Logger<TodoController>(new LoggerFactory()));
}
//test methods ...With the current library, developers have the ability to define different entry points for tests, including directly testing repo methods or directly testing controller methods. Developers also have the ability to directly test ApiClient methods. ApiClients follow the typed HttpClient pattern recommended by Microsoft. Normally, HttpClient instances are injected into an ApiClient class by the AspNetCore DI engine. When directly testing ApiClients, however, HttpClient instances must be created by the developer (or a base class) in order to manually instantiate the ApiClient. Also, the API with which the ApiClient communicates must be launched and the API's base address must be known to the ApiClient. Thankfully, the current library takes care of both of these tasks.
EDennis.AspNetCore.Base provides two base test classes for directly testing ApiClients, one class for read-only APIs and the other class for writeable APIs. Both of the test classes use ApiLauncher-like code behind the scenes to launch the target APIs. Why not simply use WebApplicationFactory? Although WebApplicationFactory provides a lot of flexibility, it doesn't provide the developer with a static base address for the ApiClient to use. The developer can subclass the WebApplicationFactory class in order to provide additional configurations, but most of this work can be avoided by using an appropriate base class from the current library.
The two ApiClientTests classes require the use of an ICollectionFixture, which injects an instance of an ApiLauncherFactory (used to launch the target API). For the developer's convenience, the library provides an abstract base class, called ApiClientTestsCollectionBase, which can be extended to provide the necessary ICollectionFixture. The developer must resolve the two type parameters (one for the ApiClient class and one for the target API's Startup class) to concrete classes.
_NOTE: All test classes that wish to use the test server spun up by the ICollectionFixture, must use the name of the collection as specified in the CollectionDefinition attribute (see below for an example). _
[CollectionDefinition("ApiClient Tests")]
public class ApiClientTestsCollection
: ApiClientTestsCollectionBase<InternalApi, Samples.Colors.InternalApi.Startup> {
}The ReadonlyApiClientTests class makes it relatively easy to directly test an ApiClient that communicates with an API project that is read-only. Such a project would not have a need for on-the-fly-generated in-memory databases during testing.
ReadonlyApiClientTests has two type parameters.
| Type Parameter | Constraints |
|---|---|
TClient |
The type of the instantiated ApiClient |
TStartup |
The Startup class of the target API to which the ApiClient points |
ReadonlyApiClientTests has a single constructor with two arguments. The arguments for the constructor are injected into the test by the unit testing runtime.
| Parameter | Description |
|---|---|
ITestOutputHelper output |
The Xunit helper class for piping messages to the test output console |
ApiLauncherFactory<TStartup> factory |
A class for launching the target API, where TStartup is the entry point |
ReadonlyApiClientRepoTests exposes three public properties.
| Property | Description |
|---|---|
public TClient ApiClient |
Provides direct access to the ApiClient object |
public ITestOutputHelper Output |
The Xunit.Abstractions helper object for writing to the test output console |
public string InstanceName |
The generated name of the in-memory database |
using A = Namespace.For.TargetApi;
//...
[Collection("ApiClient Tests")] //to use ApiClientTestsCollection (see above)
public class TodoApiClientTests : ReadonlyApiClientTests<
TodoApiClient, A.Startup> {
public TodoApiClientTests(ITestOutputHelper output,
ApiLauncher<A.Startup> factory)
: base(output, factory) { }
// test methods...
}The WriteableApiClientTests class is similar to the ReadolyApiClientTests class, except that it targets APIs with writeable operations. It is assumed that such a project would need an on-the-fly-generated in-memory databases during testing.
WriteableApiClientTests has two type parameters.
| Type Parameter | Constraints |
|---|---|
TClient |
The type of the instantiated ApiClient |
TStartup |
The Startup class of the target API to which the ApiClient points |
WriteableApiClientTests has a single constructor with two arguments. The arguments for the constructor are injected into the test by the unit testing runtime.
| Parameter | Description |
|---|---|
ITestOutputHelper output |
The Xunit helper class for piping messages to the test output console |
ApiLauncherFactory<TStartup> factory |
A class for launching the target API, where TStartup is the entry point |
WriteableApiClientRepoTests exposes three public properties.
| Property | Description |
|---|---|
public TClient ApiClient |
Provides direct access to the ApiClient object |
public ITestOutputHelper Output |
The Xunit.Abstractions helper object for writing to the test output console |
public string InstanceName |
The generated name of the in-memory database |
using A = Namespace.For.TargetApi;
//...
[Collection("ApiClient Tests")] //to use ApiClientTestsCollection (see above)
public class TodoApiClientTests : WriteableApiClientTests<
TodoApiClient, A.Startup> {
public TodoApiClientTests(ITestOutputHelper output,
ApiLauncher<A.Startup> factory)
: base(output, factory) { }
// test methods...
}Endpoint Tests are tests where the system under test is entered through an HTTP request to a specific URL. Endpoint tests are tests that most closely conform to Microsoft's examples of Integration Testing; however, they are intended to target API endpoints, rather than endpoints that return HTML.
Endpoint tests make use of a subclass of Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory. The subclass-specific code ensures that the target project's appsettings.Development.json is read into the IConfiguration singleton of the test server.
There are two kinds of endpoint tests, depending upon whether the target API has read-only operations or has writeable operations. Importantly, while the subclassed WebApplicationFactory does launch a test server for the target API, it does not launch any child APIs. Instead, these child APIs can be launched by configuring an ApiLauncher for each API. (For more details, see ApiLauncher).
The two EndpointTests classes require the use of an ICollectionFixture, which injects an instance of an ConfiguringWebApplicationFactory (used to ensure that appropriate IConfiguration is selected). For the developer's convenience, the library provides an abstract base class, called EndpointTestsCollectionBase, which can be extended to provide the necessary ICollectionFixture. The developer must resolve a single type parameter (for the target API's Startup class) to a concrete class.
_NOTE: All test classes that wish to use the test server spun up by the ICollectionFixture, must use the name of the collection as specified in the CollectionDefinition attribute (see below for an example). _
[CollectionDefinition("Endpoint Tests")]
public class EndpointTestsCollection
: EndpointTestsCollectionBase<Startup> {
}The ReadonlyEndpointTests class facilitates development of tests that target read-only APIs -- or more specifically, those APIs that do not directly or indirectly (e.g., through a child API) insert, update, or delete records in a database. It is assumed that those kinds of APIs would have no need for an generated-on-the-fly in-memory databases for testing.
ReadonlyEndpointTests has one type parameter.
| Type Parameter | Constraints |
|---|---|
TStartup |
The Startup class of the target API |
ReadonlyEndpointTests has a single constructor with two arguments. The arguments for the constructor are injected into the test by the unit testing runtime.
| Parameter | Description |
|---|---|
ITestOutputHelper output |
The Xunit helper class for piping messages to the test output console |
ConfiguringWebApplicationFactory<TStartup> |
A class for launching the target API, where TStartup is the entry point |
ReadonlyEndpointTests exposes three public properties.
| Property | Description |
|---|---|
public HttpClient HttpClient |
Provides direct access to the HttpClient object |
public ITestOutputHelper Output |
The Xunit.Abstractions helper object for writing to the test output console |
public string InstanceName |
The generated name of the in-memory database |
using A = Namespace.For.TargetApi;
//...
[Collection("Endpoint Tests")]
public class TodoEndpointTests : ReadonlyEndpointTests<A.Startup> {
public TodoEndpointTests(ITestOutputHelper output,
ConfiguringWebApplicationFactory<A.Startup> factory)
: base(output, factory) { }
// test methods...
}The WriteableEndpointTests class facilitates development of tests that target writeable APIs -- or more specifically, those APIs that may directly or indirectly (e.g., through a child API) insert, update, or delete records in a database. It is assumed that those kinds of APIs have need for an generated-on-the-fly in-memory databases for testing.
WriteableEndpointTests has one type parameter.
| Type Parameter | Constraints |
|---|---|
TStartup |
The Startup class of the target API |
WriteableEndpointTests has a single constructor with two arguments. The arguments for the constructor are injected into the test by the unit testing runtime.
| Parameter | Description |
|---|---|
ITestOutputHelper output |
The Xunit helper class for piping messages to the test output console |
ConfiguringWebApplicationFactory<TStartup> |
A class for launching the target API, where TStartup is the entry point |
WriteableEndpointTests exposes three public properties.
| Property | Description |
|---|---|
public HttpClient HttpClient |
Provides direct access to the HttpClient object |
public ITestOutputHelper Output |
The Xunit.Abstractions helper object for writing to the test output console |
public string InstanceName |
The generated name of the in-memory database |
using A = Namespace.For.TargetApi;
//...
[Collection("Endpoint Tests")]
public class TodoEndpointTests : WriteableEndpointTests<A.Startup> {
public TodoEndpointTests(ITestOutputHelper output,
ConfiguringWebApplicationFactory<A.Startup> factory)
: base(output, factory) { }
// test methods...
}Five additional classes provide support to the base test classes described above.
| Class | Description |
|---|---|
| ConfigurationFactory | Ensures that the application Configuration object is built from the correct appsettings.Development.json |
| ConfiguringWebApplicationFactory | Extends WebApplicationFactory and also provides the same functionality as ConfigurationFactory |
| ApiLauncherFactory | Launches a target API and also provides the same functionality as ConfigurationFactory |
| TestHttpClientFactory | Creates HttpClient instances for use in tests |
| ClassInfo | Finds the appropriate appsettings.Development.json for a target class |