Skip to content

Instantly share code, notes, and snippets.

@Elfocrash
Created July 10, 2019 10:40
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save Elfocrash/101ffc29947832545cdaebcb259c2f44 to your computer and use it in GitHub Desktop.
Save Elfocrash/101ffc29947832545cdaebcb259c2f44 to your computer and use it in GitHub Desktop.
ASP.NET Core Integration tests code from my video: https://www.youtube.com/watch?v=7roqteWLw4s
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Tweetbook.Contracts.V1;
using Tweetbook.Contracts.V1.Requests;
using Tweetbook.Contracts.V1.Responses;
using Tweetbook.Data;
namespace Tweetbook.IntegrationTests
{
public class IntegrationTest
{
protected readonly HttpClient TestClient;
protected IntegrationTest()
{
var appFactory = new WebApplicationFactory<Startup>()
.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
services.RemoveAll(typeof(DataContext));
services.AddDbContext<DataContext>(options => { options.UseInMemoryDatabase("TestDb"); });
});
});
TestClient = appFactory.CreateClient();
}
protected async Task AuthenticateAsync()
{
TestClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", await GetJwtAsync());
}
protected async Task<PostResponse> CreatePostAsync(CreatePostRequest request)
{
var response = await TestClient.PostAsJsonAsync(ApiRoutes.Posts.Create, request);
return await response.Content.ReadAsAsync<PostResponse>();
}
private async Task<string> GetJwtAsync()
{
var response = await TestClient.PostAsJsonAsync(ApiRoutes.Identity.Register, new UserRegistrationRequest
{
Email = "test@integration.com",
Password = "SomePass1234!"
});
var registrationResponse = await response.Content.ReadAsAsync<AuthSuccessResponse>();
return registrationResponse.Token;
}
}
}
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Tweetbook.Contracts.V1;
using Tweetbook.Contracts.V1.Requests;
using Tweetbook.Domain;
using Xunit;
namespace Tweetbook.IntegrationTests
{
public class PostsControllerTests : IntegrationTest
{
[Fact]
public async Task GetAll_WithoutAnyPosts_ReturnsEmptyResponse()
{
// Arrange
await AuthenticateAsync();
// Act
var response = await TestClient.GetAsync(ApiRoutes.Posts.GetAll);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
(await response.Content.ReadAsAsync<List<Post>>()).Should().BeEmpty();
}
[Fact]
public async Task Get_ReturnsPost_WhenPostExistsInTheDatabase()
{
// Arrange
await AuthenticateAsync();
var createdPost = await CreatePostAsync(new CreatePostRequest {Name = "Test post"});
// Act
var response = await TestClient.GetAsync(ApiRoutes.Posts.Get.Replace("{postId}", createdPost.Id.ToString()));
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var returnedPost = await response.Content.ReadAsAsync<Post>();
returnedPost.Id.Should().Be(createdPost.Id);
returnedPost.Name.Should().Be("Test post");
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.7.0" />
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Tweetbook\Tweetbook.csproj" />
</ItemGroup>
</Project>
@FlippieCoetser
Copy link

Hi Nick, I noticed when you use the HTTP Client you do not pass a URI into the GetAsync() method.
For test:

  1. you pass ApiRoutes.Posts.GetAll as an argument.
  2. you pass ApiRoutes.Posts.Get.Replace("{postId}", createdPost.Id.ToString()) as argument.

Rather than manually creating the URI String, the technique you use is much more elegant.
Can you help me out with a bit more info? where does ApiRoutes.Posts.* comes from and how I can do something similar?

@Elfocrash
Copy link
Author

Hey 👋. The ApiRoutes is a class I created in the main project to manage the application’s uris in an elegant way. It’s manually created and it can be found in the project’s main repo under Contracts.

@FlippieCoetser
Copy link

Just Perfect! I am going to 'steal' this technique. Thanks, Nick!

@SilasAlvesJunior
Copy link

Awesome!!!!
What great way to make integrations test!!!! Really appreciate that!

@Yousif-FJ
Copy link

Yousif-FJ commented Apr 7, 2020

Hi, Thanks for your work.
I would like to note that "services.RemoveAll(typeof(DataContext));" didn't work for me because The test used the actual DB context not the InMemory DB.
I replaced it with

            var descriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbContextOptions<ApplicationDbContext>));

            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

Like Ms docs

@Qassamala
Copy link

Hi, Thanks for your work.
I would like to note that "services.RemoveAll(typeof(DataContext));" didn't work for me because The test used the actual DB context not the InMemory DB.
I replaced it with

            var descriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbContextOptions<ApplicationDbContext>));

            if (descriptor != null)
            {
                services.Remove(descriptor);
            }

Like Ms docs

Had the same issue. Works for me now with your replacement.

@sravanithamatamqualbrain
Copy link

sravanithamatamqualbrain commented Jul 9, 2020

Can you please give the path seeing ApiRoutes.cs class. I can not find it anywhere.

@trolit
Copy link

trolit commented Aug 22, 2020

Can you please give the path seeing ApiRoutes.cs class. I can not find it anywhere.

@sravanithamatamqualbrain
I don't know if Nick will be mad at me because he might want you to subscribe in order to get source code or smth. Idea is simple. You need to maneuver with static keyword and nested classes. Example(assuming that controller name is: UsersController):

public static class ApiRoutes 
{
  private static readonly string _baseUrl = "https://localhost:blabla/api/";

  public static class Users
  {
     private static readonly string _usersControllerUrl = string.Concat(_baseUrl, "users");
    
     public static readonly string GetAll = _usersControllerUrl;

     public static readonly string Get = string.Concat(_usersControllerUrl, "/{userId}");

     public static readonly string Delete = string.Concat(_usersControllerUrl, "/{userId}");
  }
}

@Elfocrash
Copy link
Author

Thanks for posting this @trolit, I should have proivided this myself but I had the notifications suppressed.

@danich93
Copy link

@trolit exactly what I came here for, thanks 👍

@gichamba
Copy link

Hey 👋. The ApiRoutes is a class I created in the main project to manage the application’s uris in an elegant way. It’s manually created and it can be found in the project’s main repo under Contracts.

Where do we find the main project?

@swarooprooney
Copy link

I am trying to integration test my .NET 5 project using similar setup to what has been described in the video. The unit test run fine when I run them individually but when I run them all at once first to be executed passes and rest fail, when I debugged it was because the user already existed. I think the context is being shared among the tests but I do not know how to not share it. Any help please?

@swarooprooney
Copy link

Ok I figured it out. I had to give different database name for each run
the code is as below
var dbName = Guid.NewGuid().ToString(); var appFactory = new WebApplicationFactory<Startup>() .WithWebHostBuilder(builder => { builder.ConfigureServices(services => { var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<DataContext>)); if (descriptor != null) { services.Remove(descriptor); } services.AddDbContext<DataContext>(options => { options.UseInMemoryDatabase(dbName); }); }); }); TestClient = appFactory.CreateClient();

@jarrelpothoff
Copy link

i got a null reference error on variable response in GetJwtAsync() method. I copied the same code. I use .net core 5.0 web api. Anyone know the fix?

`private async Task GetJwtAsync()
{
var response = await TestClient.PostAsJsonAsync(ApiRoutes.Identity.Register, new UserRegistrationRequest
{
Email = "test@integration.com",
Password = "SomePass1234!"
});

            var registrationResponse = await response.Content.ReadAsAsync<AuthSuccessResponse>();
            return registrationResponse.Token;
        }`

@ivandamyanov
Copy link

Hey 👋. The ApiRoutes is a class I created in the main project to manage the application’s uris in an elegant way. It’s manually created and it can be found in the project’s main repo under Contracts.

Where do we find the main project?

I have found this repo where credits were also given to Nick: https://github.com/MohamedAshraf004/TweetbookAPI

@jsdevtom
Copy link

Hey wave. The ApiRoutes is a class I created in the main project to manage the application’s uris in an elegant way. It’s manually created and it can be found in the project’s main repo under Contracts.

Where do we find the main project?

I have found this repo where credits were also given to Nick: https://github.com/MohamedAshraf004/TweetbookAPI

This repo is no longer available

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment