Skip to content
Open
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
352 changes: 191 additions & 161 deletions AutoFixtureExtensions.sln

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ The following packages are available:
* **[Kralizek.AutoFixture.Extensions.Grpc](src/Grpc)**<br/>
An integration between AutoFixture and [Grpc.Core.Testing](https://www.nuget.org/packages/Grpc.Core.Testing/) to test GRPC services and components consuming GRPC clients.

* **[Kralizek.AutoFixture.Extensions.Grpc.Server](src/Grpc.Server)**<br/>
An integration between AutoFixture, [Grpc.Core.Testing](https://www.nuget.org/packages/Grpc.Core.Testing/) and [Microsoft.AspNetCore.Mvc.Testing](https://www.nuget.org/packages/Microsoft.AspNetCore.Mvc.Testing) to test GRPC services hosted by ASP.NET Core applications.

## License
The content of this repository is licensed under the [MIT license](https://github.com/Kralizek/AutoFixtureExtensions/blob/master/LICENSE.txt).
27 changes: 27 additions & 0 deletions helpers/Grpc/TestGrpcService/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:64589/",
"sslPort": 44377
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"TestGrpcService": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}
2 changes: 1 addition & 1 deletion helpers/Grpc/TestGrpcService/Services/GreeterService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public GreeterService(ILogger<GreeterService> logger)
_logger = logger;
}

static string FormatMessage(string name) => $"Hello {name}";
public static string FormatMessage(string name) => $"Hello {name}";

public override Task<HelloReply> SayHelloUnary(HelloRequest request, ServerCallContext context)
{
Expand Down
18 changes: 18 additions & 0 deletions src/Grpc.Server/Grpc.Server.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
<PackageTags>autofixture;unit testing;test;mock;aspnetcore;grpc;grpc services</PackageTags>
<Description>An extension to AutoFixture to easily test ASP.NET Core applications hosting GRPC services.</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.11.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.*" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.37.0" />
</ItemGroup>

</Project>
28 changes: 28 additions & 0 deletions src/Grpc.Server/GrpcServerFixtureExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using Grpc.Core;
using Kralizek.AutoFixture.Extensions.Internal;
using Microsoft.AspNetCore.Hosting;

// ReSharper disable CheckNamespace

namespace AutoFixture
{
public static class GrpcServerFixtureExtensions
{
private static readonly Action<IWebHostBuilder> EmptyAction = _ => { };

public static IFixture AddGrpcServerSupport<TClient, TEntryPoint>(this IFixture fixture, Action<IWebHostBuilder>? configuration = null)
where TEntryPoint : class
where TClient : ClientBase<TClient>
{
if (fixture is null)
{
throw new ArgumentNullException(nameof(fixture));
}

fixture.Customize(new GrpcServerCustomization<TClient, TEntryPoint>(configuration ?? EmptyAction));

return fixture;
}
}
}
61 changes: 61 additions & 0 deletions src/Grpc.Server/Internal/GrpcClientSpecimenBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using AutoFixture;
using AutoFixture.Kernel;
using Grpc.Core;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;

namespace Kralizek.AutoFixture.Extensions.Internal
{
public class GrpcClientSpecimenBuilder<TClient, TEntryPoint> : ISpecimenBuilder
where TEntryPoint : class
where TClient : ClientBase<TClient>
{
public IRequestSpecification RequestSpecification { get; }

public GrpcClientSpecimenBuilder(IRequestSpecification requestSpecification)
{
RequestSpecification = requestSpecification ?? throw new ArgumentNullException(nameof(requestSpecification));
}

public GrpcClientSpecimenBuilder() : this(new GrpcClientRequestSpecification())
{
}

public object Create(object request, ISpecimenContext context)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}

if (context is null)
{
throw new ArgumentNullException(nameof(context));
}

if (!RequestSpecification.IsSatisfiedBy(request))
{
return new NoSpecimen();
}

var factory = context.Create<WebApplicationFactory<TEntryPoint>>();

var services = new ServiceCollection();

services.AddGrpcClient<TClient>(o => o.Address = factory.Server.BaseAddress)
.ConfigurePrimaryHttpMessageHandler(() => factory.Server.CreateHandler());

var provider = services.BuildServiceProvider();

var client = provider.GetRequiredService<TClient>();

return client;
}

public class GrpcClientRequestSpecification : IRequestSpecification
{
public bool IsSatisfiedBy(object request) => request is Type type && type == typeof(TClient);
}
}
}
28 changes: 28 additions & 0 deletions src/Grpc.Server/Internal/GrpcServerCustomization.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using AutoFixture;
using Grpc.Core;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;

namespace Kralizek.AutoFixture.Extensions.Internal
{
public class GrpcServerCustomization<TClient, TEntryPoint> : ICustomization
where TEntryPoint : class
where TClient : ClientBase<TClient>

{
private readonly Action<IWebHostBuilder> _configuration;

public GrpcServerCustomization(Action<IWebHostBuilder> configuration)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}

public void Customize(IFixture fixture)
{
fixture.Inject(new WebApplicationFactory<TEntryPoint>().WithWebHostBuilder(_configuration));

fixture.Customizations.Add(new GrpcClientSpecimenBuilder<TClient, TEntryPoint>());
}
}
}
39 changes: 39 additions & 0 deletions tests/Tests.Grpc.Server/CustomAutoDataAttributes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using AutoFixture;
using AutoFixture.AutoMoq;
using AutoFixture.NUnit3;

namespace Tests
{
[AttributeUsage(AttributeTargets.Method)]
public class CustomAutoDataAttribute : AutoDataAttribute
{
public CustomAutoDataAttribute() : base(FixtureHelpers.CreateFixture)
{
}
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class CustomInlineAutoDataAttribute : InlineAutoDataAttribute
{
public CustomInlineAutoDataAttribute(params object[] args) : base(FixtureHelpers.CreateFixture, args)
{
}
}

internal static class FixtureHelpers
{
public static IFixture CreateFixture()
{
var fixture = new Fixture();

fixture.Customize(new AutoMoqCustomization
{
ConfigureMembers = true,
GenerateDelegates = true
});

return fixture;
}
}
}
26 changes: 26 additions & 0 deletions tests/Tests.Grpc.Server/GrpcServerFixtureExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Linq;
using AutoFixture;
using Kralizek.AutoFixture.Extensions.Internal;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using NUnit.Framework;
using Greeter = TestGrpc.Greeter;

namespace Tests
{
public class GrpcServerFixtureExtensionsTests
{
[Test, CustomAutoData]
public void AddGrpcServerSupport_registers_customization(IFixture fixture)
{
GrpcServerFixtureExtensions.AddGrpcServerSupport<Greeter.GreeterClient, TestGrpcService.Startup>(fixture, b => b.UseSolutionRelativeContentRoot("helpers"));

Assert.Multiple(() =>
{
Assert.That(() => fixture.Create<WebApplicationFactory<TestGrpcService.Startup>>(), Throws.Nothing);

Assert.That(fixture.Customizations, Has.Exactly(1).InstanceOf<GrpcClientSpecimenBuilder<Greeter.GreeterClient, TestGrpcService.Startup>>());
});
}
}
}
54 changes: 54 additions & 0 deletions tests/Tests.Grpc.Server/Integration/IntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Threading.Tasks;
using AutoFixture;
using AutoFixture.NUnit3;
using Microsoft.AspNetCore.TestHost;
using NUnit.Framework;
using TestGrpc;

namespace Tests.Integration
{
[AttributeUsage(AttributeTargets.Method)]
public class TestAutoDataAttribute : AutoDataAttribute
{
public TestAutoDataAttribute() : base(() => new Fixture().AddGrpcServerSupport<Greeter.GreeterClient, TestGrpcService.Startup>(b => b.UseSolutionRelativeContentRoot("helpers"))) { }
}

[TestFixture]
public class IntegrationTests
{
[Test]
public async Task Should_work_with_no_auto_data_attribute()
{
var fixture = new Fixture().AddGrpcServerSupport<Greeter.GreeterClient, TestGrpcService.Startup>(b => b.UseSolutionRelativeContentRoot("helpers"));

var name = fixture.Create<string>();

var client = fixture.Create<Greeter.GreeterClient>();

var response = await client.SayHelloUnaryAsync(new HelloRequest { Name = name });

Assert.That(response.Message, Is.EqualTo(GreeterService.FormatMessage(name)));
}

[Test, AutoData]
public async Task Should_work_with_basic_auto_data_attribute(IFixture fixture, string name)
{
fixture.AddGrpcServerSupport<Greeter.GreeterClient, TestGrpcService.Startup>(b => b.UseSolutionRelativeContentRoot("helpers"));

var client = fixture.Create<Greeter.GreeterClient>();

var response = await client.SayHelloUnaryAsync(new HelloRequest { Name = name });

Assert.That(response.Message, Is.EqualTo(GreeterService.FormatMessage(name)));
}

[Test, TestAutoData]
public async Task Should_work_with_custom_auto_data_attribute(Greeter.GreeterClient client, string name)
{
var response = await client.SayHelloUnaryAsync(new HelloRequest { Name = name });

Assert.That(response.Message, Is.EqualTo(GreeterService.FormatMessage(name)));
}
}
}
61 changes: 61 additions & 0 deletions tests/Tests.Grpc.Server/Internal/GrpcClientSpecimenBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using AutoFixture;
using AutoFixture.Idioms;
using AutoFixture.Kernel;
using Kralizek.AutoFixture.Extensions.Internal;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using NUnit.Framework;
using TestGrpc;

namespace Tests.Internal
{
[TestFixture]
public class GrpcClientSpecimenBuilderTests
{
[Test, CustomAutoData]
public void Constructor_is_guarded(GuardClauseAssertion assertion)
{
assertion.Verify(typeof(GrpcClientSpecimenBuilder<Greeter.GreeterClient, TestGrpcService.Startup>).GetConstructors());
}

[Test, CustomAutoData]
public void RequestSpecification_exposes_passed_specification(IRequestSpecification requestSpecification)
{
var sut = new GrpcClientSpecimenBuilder<Greeter.GreeterClient, TestGrpcService.Startup>(requestSpecification);

Assert.That(sut.RequestSpecification, Is.SameAs(requestSpecification));
}

[Test, CustomAutoData]
public void Default_RequestSpecification_is_HttpClientRequestSpecification()
{
var sut = new GrpcClientSpecimenBuilder<Greeter.GreeterClient, TestGrpcService.Startup>();

Assert.That(sut.RequestSpecification, Is.InstanceOf<GrpcClientSpecimenBuilder<Greeter.GreeterClient, TestGrpcService.Startup>.GrpcClientRequestSpecification>());
}

[Test, CustomAutoData]
public void Create_is_guarded(GuardClauseAssertion assertion)
{
assertion.Verify(typeof(GrpcClientSpecimenBuilder<Greeter.GreeterClient, TestGrpcService.Startup>).GetMethod(nameof(GrpcClientSpecimenBuilder<Greeter.GreeterClient, TestGrpcService.Startup>.Create)));
}

[Test, CustomAutoData]
public void Create_returns_HttpClient_if_requested(GrpcClientSpecimenBuilder<Greeter.GreeterClient, TestGrpcService.Startup> sut, IFixture fixture)
{
fixture.Inject(new WebApplicationFactory<TestGrpcService.Startup>().WithWebHostBuilder(b => b.UseSolutionRelativeContentRoot("")));

var result = sut.Create(typeof(Greeter.GreeterClient), fixture.Create<SpecimenContext>());

Assert.That(result, Is.InstanceOf<Greeter.GreeterClient>());
}

[Test, CustomAutoData]
public void Create_returns_NoSpecimen_if_request_is_invalid(GrpcClientSpecimenBuilder<Greeter.GreeterClient, TestGrpcService.Startup> sut, SpecimenContext context)
{
var result = sut.Create(typeof(object), context);

Assert.That(result, Is.InstanceOf<NoSpecimen>());
}
}
}
Loading