Need Integration Testing crash course

I know right? I could but just going to leave it as they have it. Just writing a couple of tests to handle their great way of throwing errors LOL. Got about 12 of these endpoints.


I've been tasked with upgrading it too, to make it more adherent to modern standards
 
^

And then they tie my hands with this right at the end of the document.

Please do not modify the API contract
- Do not rename the exposed model's properties
- Do not modify the API routes

I will actually bring this up with them if I fail this test. Its like cmon guys what gives...
 
If this is part of an interview where you are going to be able to speak to people, then this all seems trivial - everything can be discussed
 
^

And then they tie my hands with this right at the end of the document.

Please do not modify the API contract in any of the following ways
- Do not rename the exposed model's properties
- Do not modify the API routes

I will actually bring this up with them if I fail this test. Its like cmon guys what gives...

You can add a lot of comments.
Just add a comment saying that you would like to [insert code here], but that would violate the contract.

It could also be that they mean what I added in bold, which could allow 404s.
 

I am back on Integration Testing now. Took a huge detour getting more into the Blazor side of things.

I am noticing some strange happenings with my WebApplicationFactory. It runs twice if I have more than one test.

Tl;dr version. I am running an InMemoryDatabase context which includes Identity. I am seeding an administrator with admin rights because the controller I am wanting to test as the attribute: [Authorize(Roles = "Administrator")].

When I have one test running, everything passes. If I add another test, this runs twice! And what happens is that two admin users get seeded and because there are two, nothing gets returned when trying to log in.

Literally every single line of the project, even the middleware, runs twice!

C#:
public class CustomWebApplicationFactory<TStartup>: WebApplicationFactory<TStartup> where TStartup : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var descriptor = services.SingleOrDefault(
                d => d.ServiceType ==
                    typeof(DbContextOptions<ApplicationDbContext>));

            services.Remove(descriptor);

            services.AddDbContext<ApplicationDbContext>(options =>
            {
                options.UseInMemoryDatabase("InMemoryDbForTesting");
            });

            var sp = services.BuildServiceProvider();

            using (var scope = sp.CreateScope())
            {
                var scopedServices = scope.ServiceProvider;
                var db = scopedServices.GetRequiredService<ApplicationDbContext>();

                db.Database.EnsureCreated();

                var userManager = scopedServices.GetRequiredService<UserManager<ApplicationUser>>();
                var roleManager = scopedServices.GetRequiredService<RoleManager<IdentityRole>>();

                Utilities.SeedDefaultUserAsync(userManager, roleManager);
            }
        });
    }
}

Test

C#:
public class JobTypeControllerTests :
    IClassFixture<CustomWebApplicationFactory<Startup>>
{
    private readonly HttpClient _client;
    private readonly CustomWebApplicationFactory<Startup>
        _factory;

    public JobTypeControllerTests(
        CustomWebApplicationFactory<Startup> factory)
    {
        _factory = factory;
        _client = factory.CreateClient(new WebApplicationFactoryClientOptions
        {
            AllowAutoRedirect = false
        });
    }

    public async Task AuthenticateAsync()
    {
        _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", await GetJwtAsync());
    }

    private async Task<string> GetJwtAsync()
    {
        var login = new UserLoginRequest
        {
            Email = "[email protected]",
            Password = "Qaz12!"
        };

        var response = await _client.PostAsJsonAsync(ApiRoutes.Identity.Login, login);
        var loginResponse = await response.Content.ReadAsAsync<AuthenticationResult>();

        return loginResponse.Token;
    }

    [Fact]
    public async Task GetById_WithItemNotExists_ReturnsNotFound()
    {
        // Arrange
        await AuthenticateAsync();

        // Act
        var response = await _client.GetAsync(ApiRoutes.JobTypes.GetById.Replace("{id}", "12"));

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }
}
 
Last edited:
That’s expected. You need to call CreateClient in your test.
 
That’s expected. You need to call CreateClient in your test.

This was the issue I was talking about earlier with the WebApplicationFactory running twice. Looks like it has been put into the sprint backlog for now.


 
Last edited:
I got it working eventually. With your suggestion to CreateClient in every test and by adding the following to the scaffolding (A suggestion made on one of those Github issues) that finally resolved and all tests passing.

inmemoryresolved.jpg
 
glad you got it sorted.
although it makes sense for the "Startup" to execute every time, it didnt make sense that your DB had data from the last run
 
Edit: Resolved see next post.

So my Integration Tests working and completed. I have moved on now to Unit Testing, specifically I am testing the controller.

There is a small issue I am having with ModelState validation not working (or working but I'm testing it incorrectly)

In order to create a job type a Description and Duration is required. I have left out Description for the sake of the test (inserting an invalid object model).

Now this should pass, bit it is not. The job type is still being created and I am getting a 201 response. Can anyone spot what is is I am doing wrong? I can't come right with this.

Test
C#:
[Fact]
public async Task Add_InvalidObjectPassed_ReturnsBadRequest()
{
    // Arrange
    var nameMissingItem = new CreateJobTypeRequest()
    {
        //Description = "Test Description",
        Duration = 12.00M
    };

    _controller.ModelState.AddModelError("Description", "Required");

    // Act
    var badResponse = await _controller.Create(nameMissingItem);

    // Assert
    Assert.IsType<BadRequestObjectResult>(badResponse);
}

Controller
C#:
// POST api/v1/jobtypes
[Authorize(Roles = "Administrator")]
[HttpPost(ApiRoutes.JobTypes.Create)]
public async Task<IActionResult> Create([FromBody] CreateJobTypeRequest request)
{
    var jobType = await _jobtypeService.CreateAsync(request);
    return CreatedAtAction(nameof(GetById), new { id = jobType.Id }, jobType);
}

DtoModel
C#:
public class CreateJobTypeRequest
{
    [Required]
    [StringLength(150)]
    [Column(TypeName = "varchar(150)")]
    public string Description { get; set; }

    [Required]
    [Column(TypeName = "decimal(18,2)")]
    public decimal Duration { get; set; }
}
 
Last edited:
Ok that's weird. I had to add this into my controller. I've never had to do that I always seen the modelstate validated automatically by the API .NET gears and things.

if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
 
If I'm not mistaken you can toggle the automatic validation in the application setup, probably in Startup.cs
 
If I'm not mistaken you can toggle the automatic validation in the application setup, probably in Startup.cs

I'll have to check that out thanks. Testing against the endpoints the validation works, but against the controllers themselves is where it seems to hit a miss.
 
To be young and still coding on weekends. I'm sitting here on the couch too lazy to take my 30 something self to the fridge to fetch another beer.

Disgusting how lazy I am.

Zzzzzzzzzz
 
Last edited:
Top
Sign up to the MyBroadband newsletter
X