Testing a controller

Once we have implemented the InMemoryApplicationFactory class, it is possible to utilize it by implementing the IClassFixture interface in our test classes. Therefore, let's start by initializing a new ItemControllerTests class in the Catalog.API.Tests project:

using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Shouldly;
using Catalog.Domain.Infrastructure.Entities;
using Catalog.Fixtures;
using Xunit;

namespace Catalog.API.Tests.Controllers
{
public class ItemControllerTests : IClassFixture<InMemoryApplicationFactory<Startup>>
{
private readonly InMemoryApplicationFactory<Startup> _factory;
public ItemControllerTests(InMemoryApplicationFactory<Startup>
factory)
{
_factory = factory;
}
....
}
}

The ItemControllerTests class provides remarkable test coverage for the action methods. First of all, the test class implements the generic IClassFixture interface provided by the xUnit.Sdk package. The IClassFixture interface refers to the previously defined InMemoryApplicationFactory<Startup> and it injects the new instance of the factory class into the constructor of the test classes. Consequently, a new instance of the factory will be provided for each test class executed.

Let's take a look at the test methods that cover the get operation of ItemController:

..
[Theory]
[InlineData("/api/items/")]
public async Task get_should_return_success(string url)

{
var client = _factory.CreateClient();
var response = await client.GetAsync(url);

response.EnsureSuccessStatusCode();
}

[Fact]
public async Task get_by_id_should_return_item_data()
{
const string id = "86bff4f7-05a7-46b6-ba73-d43e2c45840f";
var client = _factory.CreateClient();
var response = await client.GetAsync($"/api/items/{id}");

response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var responseEntity = JsonConvert.
DeserializeObject<Item>(responseContent);

responseEntity.ShouldNotBeNull();
}

The preceding implementation uses the CreateClient method provided by InMemoryApplicationFactory<Startup> to initialize a new HttpClient instance. Therefore, if we take the get_by_id_should_return_item_data method as an example, it uses the client to call the /api/items/{id} route and checks that the information returned is not nullWe can proceed by testing the add item operation by adding the following test methods to the ItemControllerTests class:

[Fact]
public async Task add_should_create_new_record()
{
var request = new AddItemRequest
{
Name = "Test album",
Description = "Description",
LabelName = "Label name",
Price = new Price { Amount = 13, Currency = "EUR" },
PictureUri = "https://mycdn.com/pictures/32423423",
ReleaseDate = DateTimeOffset.Now,
AvailableStock = 6,
GenreId = new Guid("c04f05c0-f6ad-44d1-a400-3375bfb5dfd6"),
ArtistId = new Guid("f08a333d-30db-4dd1-b8ba-3b0473c7cdab")
};

var client = _factory.CreateClient();

var httpContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
var response = await client.PostAsync($"/api/items", httpContent);

response.EnsureSuccessStatusCode();
response.Headers.Location.ShouldNotBeNull();
}

Consequently, we can choose a similar approach for the Put action method implemented in the controller:

[Fact]
public async Task update_should_modify_existing_item()
{
var client = _factory.CreateClient();

var request = new EditItemRequest
{
Id = new Guid("b5b05534-9263-448c-a69e-0bbd8b3eb90e"),
Name = "Test album",
Description = "Description updated",
LabelName = "Label name",
Price = new Price { Amount = 50, Currency = "EUR" },
PictureUri = "https://mycdn.com/pictures/32423423",
ReleaseDate = DateTimeOffset.Now,
AvailableStock = 6,
GenreId = new Guid("c04f05c0-f6ad-44d1-a400-3375bfb5dfd6"),
ArtistId = new Guid("f08a333d-30db-4dd1-b8ba-3b0473c7cdab")
};

var httpContent = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
var response = await client.PutAsync($"/api/items/{request.Id}", httpContent);

response.EnsureSuccessStatusCode();

var responseContent = await response.Content.ReadAsStringAsync();
var responseEntity = JsonConvert.DeserializeObject<Item>(responseContent);

responseEntity.Name.ShouldBe(request.Name);
responseEntity.Description.ShouldBe(request.Description);
responseEntity.GenreId.ShouldBe(request.GenreId);
responseEntity.ArtistId.ShouldBe(request.ArtistId);
}

The add_should_create_new_record test method and the update_should_modify_existing_item method adopt the corresponding approach to test the Post and Put requests and the corresponding action methods. In this case, we are using the same request objects we defined for the ItemServiceTests and ItemRepositoryTests classes.

We can proceed by executing the previously implemented tests by running the dotnet test command in the solution folder, or by using the test runner of our preferred IDE. Later in this next subsection, we will look at how to optimize the initialization of the request and hold the test data at a unique point.

Using IClassFixture implies that the same InMemoryApplicationFactory instance will be shared by all the test methods. Therefore, we will have the same underlying data for every test method. If we want to keep the tests fully isolated, we can avoid the use of the class fixture and initialize a new InMemoryApplicationFactory instance in the constructor of the test class: 
 public class ItemControllerTests
{
private readonly InMemoryApplicationFactory<Startup> _factory;

public ItemControllerTests()
{
_factory = new InMemoryApplicationFactory<Startup>();
}
....
}
This approach also guarantees isolation between every single test method implemented in the test class. Furthermore, the constructor will provide a new instance every time.

Next, let's have a look at how to load test data using xUnit data attributes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.39.74