The original way I wrote integration tests for my GraphQL endpoints was inspired by this blog post: https://chillicream.com/blog/2019/04/11/integration-tests
As explained in this post you can create a schema instance and directly invoke it:
public class GraphQLTests:BaseTests { | |
public GraphQLTests(ITestOutputHelper helper):base(helper) | |
{ | |
} | |
protected override void BuildUpContainer(ContainerBuilder containerBuilder) | |
{ | |
var serviceCollection = new ServiceCollection(); | |
new Startup().ConfigureServices(serviceCollection); | |
containerBuilder.Populate(serviceCollection); | |
base.BuildUpContainer(containerBuilder); | |
} | |
[Theory] | |
[InlineData("zoekLandbouwers")] | |
public async Task Queries_Succeed(string queryName) | |
{ | |
var serviceProvider = new AutofacServiceProvider(_container); | |
var schema=serviceProvider.GetRequiredService<ISchema>(); | |
var executor = schema.MakeExecutable(); | |
var query = File.ReadAllText($"GraphQLQueries\\{queryName}.graphql"); | |
IReadOnlyQueryRequest request = | |
QueryRequestBuilder.New() | |
.SetQuery(query) | |
.SetServices(serviceProvider) | |
.Create(); | |
// act | |
IExecutionResult result = await executor.ExecuteAsync(request); | |
// assert | |
result.ShouldMatchChildSnapshot(queryName); | |
} | |
} |
However this approach no longer worked after upgrading to HotChocolate 12. It started to fail with the following error message:
Autofac.Core.Registration.ComponentNotRegisteredException : The requested service 'HotChocolate.ISchema' has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.
I tried multiple approaches to get rid of this error but after a few attempts I rewrote the tests to use the Microsoft.AspNetCore.Mvc.Testing package.
I copied a few Helper classes that I found in the HotChocolate repo to make this easier in combination with GraphQL.
using Newtonsoft.Json; | |
using System.Collections.Generic; | |
using System.Text; | |
namespace GraphQL.Tests | |
{ | |
public class ClientQueryRequest | |
{ | |
[JsonProperty("id")] | |
public string Id { get; set; } | |
[JsonProperty("operationName")] | |
public string OperationName { get; set; } | |
[JsonProperty("query")] | |
public string Query { get; set; } | |
[JsonProperty("variables")] | |
public Dictionary<string, object> Variables { get; set; } | |
[JsonProperty("extensions")] | |
public Dictionary<string, object> Extensions { get; set; } | |
public override string ToString() | |
{ | |
var query = new StringBuilder(); | |
if (Id is not null) | |
{ | |
query.Append($"id={Id}"); | |
} | |
if (Query is not null) | |
{ | |
if (Id is not null) | |
{ | |
query.Append("&"); | |
} | |
query.Append($"query={Query.Replace("\r", "").Replace("\n", "")}"); | |
} | |
if (OperationName is not null) | |
{ | |
query.Append($"&operationName={OperationName}"); | |
} | |
if (Variables is not null) | |
{ | |
query.Append("&variables=" + JsonConvert.SerializeObject(Variables)); | |
} | |
if (Extensions is not null) | |
{ | |
query.Append("&extensions=" + JsonConvert.SerializeObject(Extensions)); | |
} | |
return query.ToString(); | |
} | |
} | |
} |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Net; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace GraphQL.Tests | |
{ | |
public class ClientQueryResult | |
{ | |
public string ContentType { get; set; } | |
public HttpStatusCode StatusCode { get; set; } | |
public Dictionary<string, object> Data { get; set; } | |
public List<Dictionary<string, object>> Errors { get; set; } | |
public Dictionary<string, object> Extensions { get; set; } | |
} | |
} |
using Microsoft.AspNetCore.TestHost; | |
using Newtonsoft.Json; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Net; | |
using System.Net.Http; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace GraphQL.Tests | |
{ | |
public static class HttpClientExtensions | |
{ | |
public static async Task<ClientQueryResult> PostAsync( | |
this HttpClient client, | |
ClientQueryRequest request, | |
string path = "/graphql") | |
{ | |
HttpResponseMessage response = | |
await SendPostRequestAsync( | |
client, | |
JsonConvert.SerializeObject(request), | |
path); | |
if (response.StatusCode == HttpStatusCode.NotFound) | |
{ | |
return new ClientQueryResult { StatusCode = HttpStatusCode.NotFound }; | |
} | |
var json = await response.Content.ReadAsStringAsync(); | |
ClientQueryResult result = JsonConvert.DeserializeObject<ClientQueryResult>(json); | |
result.StatusCode = response.StatusCode; | |
result.ContentType = response.Content.Headers.ContentType!.ToString(); | |
return result; | |
} | |
public static async Task<IReadOnlyList<ClientQueryResult>> PostAsync( | |
this HttpClient client, | |
IReadOnlyList<ClientQueryRequest> request, | |
string path = "/graphql") | |
{ | |
HttpResponseMessage response = | |
await SendPostRequestAsync( | |
client, | |
JsonConvert.SerializeObject(request), | |
path); | |
if (response.StatusCode == HttpStatusCode.NotFound) | |
{ | |
return new[] { new ClientQueryResult { StatusCode = HttpStatusCode.NotFound } }; | |
} | |
var json = await response.Content.ReadAsStringAsync(); | |
List<ClientQueryResult> result = | |
JsonConvert.DeserializeObject<List<ClientQueryResult>>(json); | |
foreach (ClientQueryResult item in result) | |
{ | |
item.StatusCode = response.StatusCode; | |
item.ContentType = response.Content.Headers.ContentType.ToString(); | |
} | |
return result; | |
} | |
public static Task<HttpResponseMessage> SendPostRequestAsync( | |
this HttpClient client, | |
string requestBody, | |
string path) | |
{ | |
var content = new StringContent(requestBody, Encoding.UTF8, "application/json"); | |
return client.PostAsync(CreateUrl(path), content); | |
} | |
public static string CreateUrl(string path) | |
{ | |
var url = "http://localhost:5000"; | |
if (path != null) | |
{ | |
url += "/" + path.TrimStart('/'); | |
} | |
return url; | |
} | |
} | |
} |
Now I could write the same test like this:
using Autofac; | |
using Autofac.Extensions.DependencyInjection; | |
using HotChocolate; | |
using HotChocolate.Execution; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.Extensions.Configuration; | |
using Microsoft.Extensions.DependencyInjection; | |
using NSubstitute; | |
using Snapper; | |
using Snapper.Attributes; | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Net.Http; | |
using System.Text; | |
using System.Threading.Tasks; | |
using GraphQL.API; | |
using Xunit; | |
using Xunit.Abstractions; | |
namespace GraphQL.Tests { | |
public class GraphQLTests : IClassFixture<SelfHostedApi> { | |
public HttpClient Sut { get; } | |
public GraphQLTests(SelfHostedApi fixture) | |
{ | |
Sut = fixture.CreateDefaultClient(); | |
} | |
[Theory] | |
[InlineData("zoekLandbouwers")] | |
public async Task Queries_Succeed(string queryName) | |
{ | |
//Arrange | |
var query = File.ReadAllText($"GraphQLQueries\\{queryName}.graphql"); | |
// act | |
var result = await Sut.PostAsync(new ClientQueryRequest { Query = query }); | |
// assert | |
result.ShouldMatchChildSnapshot(queryName); | |
} | |
} | |
} |