-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* IVia xml doc refinement * Quickstart documentation * docs
- Loading branch information
Showing
10 changed files
with
887 additions
and
467 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,238 +1,89 @@ | ||
# DivertR | ||
|
||
.NET Dependency Injection Diversion | ||
|
||
[![nuget](https://img.shields.io/nuget/v/DivertR.svg)](https://www.nuget.org/packages/DivertR) | ||
[![build](https://github.com/devodo/DivertR/actions/workflows/build.yml/badge.svg)](https://github.com/devodo/DivertR/actions/workflows/build.yml) | ||
|
||
DivertR is similar to well known mocking frameworks like Moq or FakeItEasy but provides additional features for dynamically manipulating the dependency injection (DI) layer at runtime. | ||
You can redirect dependency calls to test doubles, such as substitute instances, mocks or delegates, and then optionally relay them back to the original services. | ||
|
||
Many developers are already enjoying the benefits of in-process component/integration testing using Microsoft's [WebApplicationFactory (TestServer)](https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests) | ||
which also lets you customise the DI configuration, e.g. to substitute test doubles, but this can only be done once (per TestServer instantiation). | ||
|
||
DivertR was born out of the need to efficiently modify DI configurations between tests running against the same TestServer instance. | ||
It has grown into a framework that facilitates testing of wired up systems, bringing a familiar unit/mocking testing style into the realm of component and integration testing, | ||
by providing features to conveniently substitute dependency behaviour (including error conditions) and verify inputs and outputs from recorded call information. | ||
|
||
For a demonstration of usage view this [WebApp Testing Sample](https://github.com/devodo/DivertR/blob/main/test/DivertR.WebAppTests/WebAppTests.cs) or continue below | ||
for a quickstart and code overview. | ||
|
||
# Quickstart | ||
DivertR is a .NET library for creating proxy test doubles such as mocks, fakes and spies. | ||
It is similar to mocking frameworks like the well known [Moq](https://github.com/moq/moq4) but provides, in addition, features for ***integration*** and ***component*** testing of wired-up systems. | ||
|
||
## Installing | ||
# Installing | ||
|
||
Install DivertR as a [NuGet package](https://www.nuget.org/packages/DivertR): | ||
|
||
Install-Package DivertR | ||
```sh | ||
Install-Package DivertR | ||
``` | ||
|
||
Or via the .NET command line interface: | ||
|
||
dotnet add package DivertR | ||
```sh | ||
dotnet add package DivertR | ||
``` | ||
|
||
## Code Overview | ||
# Feature Summary | ||
|
||
![DivertR Via](./docs/assets/images/DivertR_Via.svg) | ||
1. Test double proxy framework for mocking, faking, stubbing, spying, etc. | ||
2. Method call interception and diversion with optional relay back to the original target. | ||
3. Dynamic update and reset of proxies in a running application enabling changes between tests without requiring restart and initialisation overhead. | ||
4. Simple plugging of proxy factories into the dependency injection container by decorating and wrapping existing registrations. | ||
5. Proxies that wrap and forward to root (original) instances so tests run against the integrated system whilst modifying and spying on specific parts as needed. | ||
6. A lightweight, fluent interface for configuring proxies to redirect calls to delegates or substitute instances. | ||
7. Recording and verifying proxy calls. | ||
8. Leveraging .NET ValueTuple types for specifying named and strongly typed call arguments that can be passed and reused e.g. in call verifications. | ||
|
||
### Start with a Foo | ||
# Example Usage | ||
|
||
Given an `IFoo` interface and a `Foo` implementation: | ||
DivertR can facilitate a style of testing where you start with a fully DI wired-up system and mock out specific parts per test. | ||
For example, it can be used to write tests on a WebApp like this: | ||
|
||
```csharp | ||
public interface IFoo | ||
{ | ||
string Name { get; set; } | ||
string Echo(string input); | ||
} | ||
|
||
public class Foo : IFoo | ||
[Fact] | ||
public async Task GivenFooExistsInRepo_WhenGetFoo_ThenReturnsFoo_WithOk200() | ||
{ | ||
public string Name { get; set; } = "Foo"; | ||
|
||
public string Echo(string input) | ||
// ARRANGE | ||
var foo = new Foo | ||
{ | ||
return $"{Name}: {input}"; | ||
} | ||
} | ||
``` | ||
|
||
With the following .NET `Microsoft.Extensions.DependencyInjection.IServiceCollection` registration: | ||
|
||
```csharp | ||
IServiceCollection services = new ServiceCollection(); | ||
services.AddTransient<IFoo, Foo>(); | ||
services.AddSingleton<IExample, Example>(); // some other example registration | ||
``` | ||
|
||
### Register | ||
|
||
Create a DivertR instance and *register* one or more DI service types of interest: | ||
|
||
```csharp | ||
var diverter = new Diverter() | ||
.Register<IFoo>() | ||
.Register<IExample>(); | ||
``` | ||
|
||
The registered DivertR types are installed into the `IServiceCollection` by decorating existing DI registrations using a provided extension method: | ||
|
||
```csharp | ||
services.Divert(diverter); | ||
``` | ||
|
||
The `IServiceCollection` can now be used as usual to build the service provider and resolve dependency instances: | ||
|
||
```csharp | ||
IServiceProvider provider = services.BuildServiceProvider(); | ||
IFoo foo = provider.GetService<IFoo>(); | ||
|
||
Console.WriteLine(foo.Echo("Hello")); // "Foo: Hello" | ||
``` | ||
|
||
### Redirect | ||
|
||
At this stage the behaviour of the resolved `IFoo` instances is unchanged. However, it can be modified using | ||
a DivertR entity called a `Via` to configure a *redirect*: | ||
|
||
```csharp | ||
IVia<IFoo> fooVia = diverter.Via<IFoo>(); | ||
fooVia | ||
.To(x => x.Echo(Is<string>.Any)) // (1) | ||
.Redirect(call => $"{call.Args[0]} DivertR"); // (2) | ||
Console.WriteLine(foo.Echo("Hello")); // "Hello DivertR" | ||
``` | ||
|
||
The `Via` intercepts calls to the resolved `IFoo` instances. | ||
By default calls are simply forwarded to the original registration, in this case instances of the `Foo` class. | ||
However, after adding the redirect any calls that match the lambda expression (1) are redirected to the delegate (2). | ||
|
||
The call's arguments can be accessed from the `call.Args` property as an `object[]`. | ||
However DivertR lets you optionally provide strongly typed named arguments using a ValueTuple type as follows: | ||
|
||
```csharp | ||
IVia<IFoo> fooVia = diverter.Via<IFoo>(); | ||
fooVia | ||
.To(x => x.Echo(Is<string>.Any)) // (1) | ||
.Redirect<(string input, __)>(call => $"{call.Args.input} DivertR"); // (2) | ||
Console.WriteLine(foo.Echo("Hello")); // "Hello DivertR" | ||
``` | ||
Id = Guid.NewGuid(), | ||
Name = "Foo123" | ||
}; | ||
|
||
> The `call.Args` property is replaced with an instance of the given ValueTuple type `(string intput, __)`. | ||
_diverter | ||
.Via<IFooRepository>() // Divert IFooRepository calls | ||
.To(x => x.GetFooAsync(foo.Id)) // matching this method and argument | ||
.Redirect(() => Task.FromResult(foo)); // by redirecting to this delegate | ||
C# requires named ValueTuples to have at least two parameters. If the call only has a single parameter, as in the example above, | ||
then a dummy second parameter must be provided using the special Diverter type `__`. | ||
|
||
Once a redirect is added it will be applied to all existing and future resolved `IFoo` instances. For example if a second `IFoo` instance is resolved: | ||
|
||
```csharp | ||
IFoo foo2 = provider.GetService<IFoo>(); | ||
foo2.Name = "Foo2"; | ||
|
||
Console.WriteLine(foo2.Echo("Hello")); // "Hello DivertR" | ||
``` | ||
|
||
### Reset | ||
|
||
To *reset* resolved instances back to their original behaviour simply discard all redirects on the `Via` with the following call: | ||
|
||
```csharp | ||
fooVia.Reset(); | ||
|
||
Console.WriteLine(foo.Echo("Hello")); // "Foo: Hello" | ||
Console.WriteLine(foo2.Echo("Hello")); // "Foo2: Hello" | ||
``` | ||
|
||
So far we have only been working with a single `Via` instance, i.e. `IVia<IFoo>` bound to the `IFoo` registration type. | ||
However, testing a system would typically require using multiple `Vias` for different types. | ||
These can all be reset at once by calling: | ||
|
||
```csharp | ||
diverter.ResetAll(); | ||
``` | ||
|
||
### Relay | ||
|
||
The `Via` also lets you *relay* back to the original or *root* registration | ||
by providing the `Relay.Root` property that can be called from the body of the redirect: | ||
|
||
```csharp | ||
fooVia | ||
.To(x => x.Echo(Is<string>.Any)) | ||
.Redirect<(string input, __)>(call => | ||
{ | ||
// run test code before | ||
// ... | ||
// call root instance | ||
IFoo root = call.Relay.Root; | ||
var message = root.Echo(call.Args.input); | ||
|
||
// run test code after | ||
// ... | ||
// ACT | ||
var response = await _fooClient.GetFooAsync(foo.Id); | ||
|
||
return $"{message} Redirect"; | ||
}); | ||
|
||
Console.WriteLine(foo.Echo("Hello")); // "Foo: Hello Redirect" | ||
Console.WriteLine(foo2.Echo("Hello")); // "Foo2: Hello Redirect" | ||
``` | ||
|
||
> The `Relay.Root` property is a proxy that the `Via` connects to the current intercepted call. | ||
> Its members can only be accessed within the context of the intercepted call otherwise a `DiverterException` is thrown. | ||
### Retarget | ||
|
||
As well as redirecting to delegates you can also *retarget* to substitutes that implement the target interface (in this case `IFoo`). | ||
This includes, for example, Mock objects: | ||
|
||
```csharp | ||
IFoo root = fooVia.Relay.Root; | ||
var mock = new Mock<IFoo>(); | ||
mock | ||
.Setup(x => x.Echo(It.IsAny<string>())) | ||
.Returns((string input) => $"{root.Echo(input)} Mock"); | ||
|
||
fooVia | ||
.To() // No parameter defaults to match all calls | ||
.Retarget(mock.Object); | ||
|
||
Console.WriteLine(foo.Echo("Hello")); // "Foo: Hello Mock" | ||
Console.WriteLine(foo2.Echo("Hello")); // "Foo2: Hello Mock" | ||
``` | ||
|
||
Note the substitute/mock can also use the `Relay.Root` proxy to call the original by conveniently accessing it as a property directly from the `Via` instance. | ||
DivertR uses an ambient `AsyncLocal` context for this so it always points to the *root* of the current call. | ||
|
||
## Interfaces only | ||
|
||
By default DivertR can only be used on interface types. Classes are not supported as calls to non-virtual members | ||
cannot be intercepted, causing inconsistent and confusing behaviour. | ||
// ASSERT | ||
response.StatusCode.ShouldBe(HttpStatusCode.OK); | ||
response.Content.Id.ShouldBe(foo.Id); | ||
response.Content.Name.ShouldBe(foo.Name); | ||
} | ||
|
||
## Async support | ||
[Fact] | ||
public async Task GivenFooRepoException_WhenGetFoo_ThenReturns500InternalServerError() | ||
{ | ||
// ARRANGE | ||
_diverter | ||
.Via<IFooRepository>() | ||
.To(x => x.GetFooAsync(Is<Guid>.Any)) | ||
.Redirect(() => throw new Exception()); | ||
|
||
Task and ValueTask async calls are fully supported, e.g. if `IFoo` is extended to include an async method: | ||
// ACT | ||
var response = await _fooClient.GetFooAsync(Guid.NewGuid()); | ||
|
||
```csharp | ||
public interface IFoo | ||
{ | ||
Task<string> EchoAsync(string input); | ||
// ASSERT | ||
response.StatusCode.ShouldBe(HttpStatusCode.InternalServerError); | ||
} | ||
``` | ||
|
||
public class Foo : IFoo | ||
{ | ||
public async Task<string> EchoAsync(string input) | ||
{ | ||
await Task.Yield(); | ||
return $"{Name}: {input}"; | ||
} | ||
} | ||
# Quickstart | ||
|
||
fooVia | ||
.To(x => x.EchoAsync(Is<string>.Any)) | ||
.Redirect<(string input, __)>(async (call, args) => $"{await call.Root.EchoAsync(args.input)} Async"); | ||
For more examples and a demonstration of setting up a test harness for a WebApp see this [WebApp Testing Sample](./test/DivertR.WebAppTests/WebAppTests.cs) | ||
or follow below for a quickstart: | ||
|
||
Console.WriteLine(await foo.EchoAsync("Hello")); // "Foo: Hello Async" | ||
Console.WriteLine(await foo2.EchoAsync("Hello")); // "Foo2: Hello Async" | ||
``` | ||
* [Vias](./docs/Via.md) for creating and configuring proxies. | ||
* [Recording and Verifying](./docs/Verify.md) calls. | ||
* [Dependency Injection](./docs/DI.md) integration. | ||
* [About](./docs/About.md) DivertR. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# About | ||
|
||
DivertR is similar to well known mocking frameworks like Moq or FakeItEasy but provides additional features for dynamically manipulating the dependency injection (DI) layer at runtime. | ||
You can redirect dependency calls to test doubles, such as substitute instances, mocks or delegates, and then optionally relay them back to the original services. | ||
|
||
Many developers are already enjoying the benefits of in-process component/integration testing using Microsoft's [WebApplicationFactory (TestServer)](https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests) | ||
which also lets you customise the DI configuration, e.g. to substitute test doubles, but this can only be done once (per TestServer instantiation). | ||
|
||
DivertR was born out of the need to efficiently modify DI configurations between tests running against the same TestServer instance. | ||
It has grown into a framework that facilitates testing of wired up systems, bringing a familiar unit/mocking testing style into the realm of component and integration testing, | ||
by providing features to conveniently substitute dependency behaviour (including error conditions) and verify inputs and outputs from recorded call information. | ||
|
||
# Interfaces Only | ||
|
||
DivertR uses the .NET Standard [DispatchProxy](https://learn.microsoft.com/en-us/dotnet/api/system.reflection.dispatchproxy) to build proxies and this is limited to interface types only. | ||
Although other proxy generators that support classes such as DynamicProxy can be used, calls to non-virtual members cannot be intercepted and this can cause inconsistent behaviour e.g. when wrapping root instances. |
Oops, something went wrong.