Testing an ASP.NET Core MVC Protobuf API using HTTPClient and xUnit

The article shows how to test an ASP.NET Core MVC API using xUnit and a HTTPClient client using Protobuf for the content formatters.

Code: https://github.com/damienbod/AspNetCoreMvcProtobufFormatters

Posts in this series:

2019-01-30 Updated to ASP.NET Core 2.2
2017-08-17 Updated ASP.NET Core 2.0

The test project tests the ASP.NET Core API produced here. xUnit is used as a test framework. The xUnit dependencies can be added to the test project using NuGet in Visual Studio 2017 as well as the Microsoft.AspNetCore.TestHost package. Microsoft provide nice docs about Integration testing ASP.NET Core.

When the NuGet packages have been added, you can view these in the csproj file, or install and update directly in this file. A reference to the project containg the API is also added to the test project.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AssemblyName>AspNetCoreProtobuf.IntegrationTests</AssemblyName>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App"  />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
    <PackageReference Include="xunit.runner.console" Version="2.4.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
    <PackageReference Include="xunit" Version="2.4.1" />
    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.2.0" />
    <PackageReference Include="protobuf-net" Version="2.4.0" />
    <PackageReference Include="xunit.runners" Version="2.0.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\AspNetCoreProtobuf\AspNetCoreProtobuf.csproj" />
  </ItemGroup>

  <ItemGroup>
    <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
  </ItemGroup>

</Project>

The TestServer is used to test the ASP.NET Core API. This is setup for all the API tests.

private readonly TestServer _server;
private readonly HttpClient _client;

public ProtobufApiTests()
{
	_server = new TestServer(
		new WebHostBuilder()
		.UseKestrel()
		.UseStartup<Startup>());
	_client = _server.CreateClient();
}

HTTP GET request test

The GetProtobufDataAndCheckProtobufContentTypeMediaType test sends a HTTP GET to the test server, and requests the content as application/x-protobuf. The result is deserialized using protobuf and the header and the expected result is checked.

[Fact]
public async Task GetProtobufDataAndCheckProtobufContentTypeMediaType()
{
	// Act
	_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-protobuf"));
	var response = await _client.GetAsync("/api/values/1");
	response.EnsureSuccessStatusCode();

	var result = ProtoBuf.Serializer.Deserialize<ProtobufModelDto>(await response.Content.ReadAsStreamAsync());

	// Assert
	Assert.Equal("application/x-protobuf", response.Content.Headers.ContentType.MediaType );
	Assert.Equal("My first MVC 6 Protobuf service", result.StringValue);
}
		

HTTP POST request test

The PostProtobufData test method sends a HTTP POST request to the test server with a protobuf serialized content. The status code of the request is validated.

[Fact]
public void PostProtobufData()
{
	// HTTP GET with Protobuf Response Body
	_client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/x-protobuf"));
	
	MemoryStream stream = new MemoryStream();
	ProtoBuf.Serializer.Serialize<ProtobufModelDto>(stream, new ProtobufModelDto
	{
		Id = 2,
		Name= "lovely data",
		StringValue = "amazing this ah"
	
	});

	HttpContent data = new StreamContent(stream);

	// HTTP POST with Protobuf Request Body
	var responseForPost = _client.PostAsync("api/Values", data).Result;

	Assert.True(responseForPost.IsSuccessStatusCode);
}

The tests can be executed or debugged in Visual Studio using the Test Explorer

The tests can also be run with dotnet test in the commandline.

C:\git\damienbod\AspNetCoreProtobufFormatters\src\AspNetCoreProtobuf.IntegrationTests>dotnet test
Build started, please wait...
Build completed.

Test run for C:\git\damienbod\AspNetCoreProtobufFormatters\src\AspNetCoreProtobuf.IntegrationTests\bin\Debug\netcoreapp1.1\AspNetCoreProtobuf.IntegrationTests.dll(.NETCoreApp,Version=v1.1)
Microsoft (R) Testausführungs-Befehlszeilentool Version 15.0.0.0
Copyright (c) Microsoft Corporation. Alle Rechte vorbehalten.

Die Testausf├╝hrung wird gestartet, bitte warten...
[xUnit.net 00:00:00.5821132]   Discovering: AspNetCoreProtobuf.IntegrationTests
[xUnit.net 00:00:00.6841246]   Discovered:  AspNetCoreProtobuf.IntegrationTests
[xUnit.net 00:00:00.7273897]   Starting:    AspNetCoreProtobuf.IntegrationTests
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Executing action method AspNetCoreProtobuf.Controllers.ValuesController.Post (AspNetCoreProtobuf) with arguments (AspNetCoreProtobuf.Model.ProtobufModelDto) - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action AspNetCoreProtobuf.Controllers.ValuesController.Post (AspNetCoreProtobuf) in 137.2264ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 346.8796ms 200
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Executing action method AspNetCoreProtobuf.Controllers.ValuesController.Get (AspNetCoreProtobuf) with arguments (1) - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.Internal.ObjectResultExecutor[1]
      Executing ObjectResult, writing value Microsoft.AspNetCore.Mvc.ControllerContext.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action AspNetCoreProtobuf.Controllers.ValuesController.Get (AspNetCoreProtobuf) in 39.0599ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 43.2983ms 200 application/x-protobuf
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Executing action method AspNetCoreProtobuf.Controllers.ValuesController.Get (AspNetCoreProtobuf) with arguments (1) - ModelState is Valid
info: Microsoft.AspNetCore.Mvc.Internal.ObjectResultExecutor[1]
      Executing ObjectResult, writing value Microsoft.AspNetCore.Mvc.ControllerContext.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action AspNetCoreProtobuf.Controllers.ValuesController.Get (AspNetCoreProtobuf) in 1.4974ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 3.6715ms 200 application/x-protobuf
[xUnit.net 00:00:01.5669956]   Finished:    AspNetCoreProtobuf.IntegrationTests

Tests gesamt: 3. Bestanden: 3. Fehler: 0. Übersprungen: 0.
Der Testlauf war erfolgreich.
Testausführungszeit: 2.7499 Sekunden

appveyor CI

The project can then be connected to any build server. Appveyor is a easy one to setup and works well with github projects. Create an account and select the github repository to build. Add an appveyor.yml file to the root of your project and configure as required. Docs can be found here:
https://www.appveyor.com/docs/build-configuration/

image: Visual Studio 2017
init:
  - git config --global core.autocrlf true
install:
  - ECHO %APPVEYOR_BUILD_WORKER_IMAGE%
  - dotnet --version
  - dotnet restore
build_script:
- dotnet build
before_build:
- appveyor-retry dotnet restore -v Minimal
test_script:
- cd src/AspNetCoreProtobuf.IntegrationTests
- dotnet test

The appveyor badges can then be used in your project md file.

|                           | Build                                                                                                                                                             |       
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| .NET Core                 | [![Build status](https://ci.appveyor.com/api/projects/status/ihtrq4u81rtsty9k?svg=true)](https://ci.appveyor.com/project/damienbod/aspnetmvc6protobufformatters)  |

This would then be displayed in github as follows:

Links

https://developers.google.com/protocol-buffers/docs/csharptutorial

http://www.stackoverflow.com/questions/7774155/deserialize-long-string-with-protobuf-for-c-sharp-doesnt-work-properly-for-me

https://xunit.github.io/

https://www.appveyor.com/docs/build-configuration/

https://www.nuget.org/packages/protobuf-net/

https://github.com/mgravell/protobuf-net

http://teelahti.fi/using-google-proto3-with-aspnet-mvc/

https://github.com/damienpontifex/ProtobufFormatter/tree/master/src/ProtobufFormatter

http://www.strathweb.com/2014/11/formatters-asp-net-mvc-6/

http://blogs.msdn.com/b/webdev/archive/2014/11/24/content-negotiation-in-mvc-5-or-how-can-i-just-write-json.aspx

https://github.com/WebApiContrib/WebApiContrib.Formatting.ProtoBuf

https://damienbod.wordpress.com/2014/01/11/using-protobuf-net-media-formatter-with-web-api-2/

https://docs.microsoft.com/en-us/aspnet/core/testing/integration-testing

2 comments

  1. […] Testing an ASP.NET Core MVC Protobuf API using HTTPClient and xUnit (Damien Bowden) […]

  2. […] for ASP.NET Core by Andrew Lock. What is the Microsoft.AspNetCore metapackage? by Andrew Lock. Testing an ASP.NET Core MVC Protobuf API using HTTPClient and xUnit by Damien Bowden. ASP.NET Core MVC Anatomy (Part 1) – AddMvcCore by Steve Gordon. Fritz’s […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: