Skip to content

Unit Testing Updates #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/development/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sidebar_position: 0

GraphQL will execute sibling fields asynchronously during normal operation. This includes multiple top-level controller action calls. However, during a debugging session, having multiple fields trying to resolve themselves can play havoc with your debug cursor. If you've ever encountered a situation where the yellow line in Visual Studio seemly jumps around to random lines of code then you've experienced this issue.

At startup, it can help to disable asynchronous field resolution and instead force each field to execute in sequential order awaiting its completion before beginning the next one. Don't forget to disable this in production though, as awaiting fields individually will _**significantly**_ impact performance.
At startup, it can help to disable asynchronous field resolution and instead force each field to execute in sequential order awaiting its completion before beginning the next one.

```csharp title="Configure Debug Mode"
services.AddGraphQL(options =>
Expand All @@ -18,3 +18,6 @@ services.AddGraphQL(options =>
options.ExecutionOptions.DebugMode = true;
});
```
:::danger Performance Killer
Don't forget to disable debug mode in production though. Awaiting fields individually will _**significantly**_ impact performance.
:::
133 changes: 124 additions & 9 deletions docs/development/unit-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,73 @@ sidebar_label: Unit Testing
sidebar_position: 1
---

GraphQL ASP.NET has more than `3500 unit tests and 91% code coverage`. Much of this is powered by a test component designed to quickly build a configurable, fully mocked server instance to perform a query. It may be helpful to download the code and extend it for harnessing your own controllers.
<span className="pill">.NET 6+</span>
<br/>
<br/>

The `TestServerBuilder<TSchema>` can be found in the `graphql-aspnet-testframework` project of the primary repo and is dependent on `Moq`. As its part of the core library solution you'll want to remove the project reference to `graphql-aspnet` project and instead add a reference to the nuget package.
:::info
Your test projects must target .NET 6 or greater to use the test framework
:::

GraphQL ASP.NET has more than `3500 unit tests and 91% code coverage`. All the internal integration tests are powered by a framework designed to quickly build a configurable, fully mocked server instance to perform a query against the runtime. It may be helpful to use and extend the framework to test your own controllers.

This document explains how to perform some common test functions for your own controller methods.


## Install the Framework

Add a reference to the [Nuget Package](https://www.nuget.org/packages/GraphQL.AspNet.TestFramework) `GraphQL.AspNet.TestFramework` to your unit test project. The framework is just a class library and not dependent on any individual testing framework like NUnit or XUnit. However, it does mock some runtime only objects and, as a result, is dependent on [Moq](https://www.nuget.org/packages/Moq).

```powershell title="Install the Test Framework"
# Using the dotnet CLI
> dotnet add package GraphQL.AspNet.TestFramework

# Using Package Manager Console
> Install-Package GraphQL.AspNet.TestFramework
```



## Create a Test Server

1. Create a new instance of the `TestServerBuilder`. The builder takes in a set of flags to perform some auto configurations for common scenarios such as exposing exceptions or altering the casing of graph type names.
1. Create a new instance of the `TestServerBuilder`. The builder takes in an optional set of flags to perform some auto configurations for common scenarios such as exposing exceptions or altering the casing of graph type names.
2. Configure your test scenario

- Use `.User` to add any permissions to the mocked user account
- Use `.Authorization` to add any security policy definitions if you wish to test security
- Use `.AddGraphQL()` to mimic the functionality of schema configuration used when your application starts.
- The `TestServerBuilder` implements `IServiceCollection`, add any additional mocked services as needed to ensure your controllers are wired up correctly by the runtime.
- `TestServerBuilder` implements `IServiceCollection`. Add any additional mocked services as needed to ensure your controllers are wired up correctly by the runtime.

3. Build the server instance using `.Build()`

```csharp title="Configuring a Test Server Instance"
[Test]
public async Task MyController_InvocationTest()
{
// Arrange
var builder = new TestServerBuilder();
builder.AddGraphQL(o => {
o.AddController<MyController>();
});

// Act
var server = builder.Build();
//...

// continued...
}

```

:::tip Custom Schemas
Use `TestServerBuild<TSchema>` to test against a custom defined schema instance.
:::

## Execute a Query

1. Mock the query execution context (the object that the runtime acts on) using `.CreateQueryContextBuilder()`
2. Configure the text, variables etc. on the builder.
Follow these steps to execute a query against the runtime. If your controller is registered to the test server and the appropriate field is requested in the query, it will be invoked.

1. Create a builder to generate a mocked execution context (the object that the runtime acts on) using `.CreateQueryContextBuilder()`
2. Configure the query text, variables etc. on the builder.
3. Build the context and submit it for processing:
- Use `server.ExecuteQuery()` to process the context. `context.Result` will be filled with the final `IQueryExecutionResult` which can be inspected for resultant data fields and error messages.
- Use `server.RenderResult()` to generate the json string a client would recieve if they performed the query.
Expand All @@ -50,13 +80,20 @@ public async Task MyController_InvocationTest()
```csharp title="Executing a Test Query"
[Test]
public async Task MyController_InvocationTest()
{
// ...
{
// Arrange
var builder = new TestServerBuilder();
builder.AddGraphQL(o => {
o.AddController<MyController>();
});

var server = builder.Build();
var contextBuilder = server.CreateQueryContextBuilder();
contextBuilder.AddQueryText("query { controller { actionMethod { property1 } } }");

var context = contextBuilder.Build();

// Act
var result = await server.RenderResult(context);

/* result contains the string for:
Expand All @@ -71,5 +108,83 @@ public async Task MyController_InvocationTest()
}
*/
}
```

## Other Test Scenarios

### Throwing Exceptions
If you need to test that your controller throws an appropriate exception you can inspect the response object (instead of rendering a result). The exception will be attached to an error message generated during the query execution.

```csharp title="Testing for Thrown Exceptions"
[Test]
public async Task MyController_InvocationTest()
{
// Arrange
var builder = new TestServerBuilder();
builder.AddGraphQL(o => {
o.AddController<MyController>();
});

var server = builder.Build();
var contextBuilder = server.CreateQueryContextBuilder();
contextBuilder.AddQueryText("query { controller { actionMethod { property1 } } }");

var context = contextBuilder.Build();

// Act
// Use ExecuteQuery instead of RenderResult to obtain the response object
// highlight-next-line
var result = await server.ExecuteQuery(queryBuilder);

// Assert
// ensure a message was captured
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Messages.Count);
Assert.IsInstanceOf(typeof(MyException), result.Messages[0].Exception);
}
```

:::tip
Use `server.ExecuteQuery` to obtain a reference to the response object. This allows you to interrogate the message data before its rendered as a json document.
:::


### Authn & Authz

Test authentication and authorization scenarios by configuring both the policies the server should support and the claims or roles the user will present during the test.

```csharp
[Test]
public async Task WhenUserHasPolicy_ThenAllowExecution()
{
// Arrange
var builder = new TestServerBuilder();
builder.AddGraphQL(o => {
o.AddController<MyController>();
});

// configure the policies the server will recognize
// and the claims the user context will have.
// This specific test assumes that the controller method
// defines an authorization requirement of "policy1".
// highlight-start
builder.Authorization.AddClaimPolicy("policy1", "myClaimType", "myClaimValue");
builder.UserContext.AddUserClaim("myClaimType", "myClaimValue")
// highlight-end

var server = builder.Build();
var contextBuilder = server.CreateQueryContextBuilder();
contextBuilder.AddQueryText("query { controller { actionMethod { property1 } } }");

var context = contextBuilder.Build();

// Act
var result = await server.RenderResult(queryBuilder);

// Assert
// ....
}
```

The user context is always injected when you run a query on the test server. By default it is an anonymous user and credentials are applied when you add a claim or policy to the context during setup.

4 changes: 2 additions & 2 deletions docs/server-extensions/multipart-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ A bitwise flag enumeration allowing the inclusion of different types of values f
mpOptions.RequestMode = MultipartRequestMode.Default;
```

A bitwise flag enumeration that controls which actions the multi-part request extension. By default, both batch queries and file uploads are enabled.
A bitwise flag enumeration that controls which parts of the multi-part request extension are enabled. By default, both batch queries and file uploads are enabled.

| Option | Description |
| ------------- | ----------------- |
Expand Down Expand Up @@ -508,7 +508,7 @@ mpOptions.RequireMultipartRequestHttpProcessor = true;
| ------------- | ----------------- |
| `true` | `true`, `false` |

Determines if, when starting up the application, the extension will check that the required http processor is registered. When set to true, if the required processor is not registered and configuration exception will be thrown and the server will fail to start. This can be helpful when registering multiple extensions to ensure that a valid processor is registered such that multipart form requests will be handled correctly.
Determines if, when starting up the application, the extension will check that the required http processor is registered. When set to true, if the required processor is not registered a configuration exception will be thrown and the server will fail to start. This can be helpful when registering multiple extensions to ensure that a valid processor is registered such that multipart-form requests will be handled correctly.

## Demo Project
See the [demo projects](../reference/demo-projects.md) for a sample project utilizing [jaydenseric's apollo-upload-client](https://github.com/jaydenseric/apollo-upload-client) as a front end for performing file uploads against this extension.