How to test a controller POST method which returns no data in response content in .NET Core 3.1?

Viewed 714

i am new to integration tests. I have a controller method which adds a user to the database, as shown below:

[HttpPost]
public async Task<IActionResult> CreateUserAsync([FromBody] CreateUserRequest request)
{
    try
    {
        var command = new CreateUserCommand
        {
            Login = request.Login,
            Password = request.Password,
            FirstName = request.FirstName,
            LastName = request.LastName,
            MailAddress = request.MailAddress,
            TokenOwnerInformation = User
        };

        await CommandBus.SendAsync(command);

        return Ok();
    }
    catch (Exception e)
    {
        await HandleExceptionAsync(e);
                
        return StatusCode(StatusCodes.Status500InternalServerError,
            new {e.Message});
    }
}

As you have noticed my method returns no information about the user which has been added to the database - it informs about the results of handling a certain request using the status codes. I have written an integration test to check is it working properly:

        [Fact]
        public async Task ShouldCreateUser()
        {
            // Arrange
            var createUserRequest = new CreateUserRequest
            {
                Login = "testowyLogin",
                Password = "testoweHaslo",
                FirstName = "Aleksander",
                LastName = "Kowalski",
                MailAddress = "akowalski@onet.poczta.pl"
            };
            var serializedCreateUserRequest = SerializeObject(createUserRequest);
            
            // Act
            var response = await HttpClient.PostAsync(ApiRoutes.CreateUserAsyncRoute,
                serializedCreateUserRequest);
            
            // Assert
            response
                .StatusCode
                .Should()
                .Be(HttpStatusCode.OK);
        }

I am not sure is it enough to assert just a status code of response returned from the server. I am confused because, i don't know, shall i attach to assert section code, which would get all the users and check does it contain created user for example. I don't even have any id of such a user because my application finds a new id for the user while adding him/her to the database. I also have no idea how to test methods like that:

        [HttpGet("{userId:int}")]
        public async Task<IActionResult> GetUserAsync([FromRoute] int userId)
        {
            try
            {
                var query = new GetUserQuery
                {
                    UserId = userId,
                    TokenOwnerInformation = User
                };
                
                var user = await QueryBus
                    .SendAsync<GetUserQuery, UserDto>(query);

                var result = user is null
                    ? (IActionResult) NotFound(new
                        {
                            Message = (string) _stringLocalizer[UserConstants.UserNotFoundMessageKey]
                        })
                    : Ok(user);

                return result;
            }
            catch (Exception e)
            {
                await HandleExceptionAsync(e);
                
                return StatusCode(StatusCodes.Status500InternalServerError,
                    new {e.Message});
            }
        }

I believe i should somehow create a user firstly in Arrange section, get it's id and then use it in Act section with the GetUserAsync method called with the request sent by HttpClient. Again the same problem - no information about user is returned, after creation (by the way - it is not returned, because of my CQRS design in whole application - commands return no information). Could you please explain me how to write such a tests properly? Have i missed anything? Thanks for any help.

2 Answers

This is how I do it:

var response = (CreatedResult) await _controller.Post(createUserRequest);
response.StatusCode.Should().Be(StatusCodes.Status201Created);

The second line above is not necessary, just there for illustration.

Also, your response it's better when you return a 201 (Created) instead of the 200(OK) on Post verbs, like:

return Created($"api/users/{user.id}", user);

To test NotFound's:

var result = (NotFoundObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status404NotFound);

The NotFoundObjectResult assumes you are returning something. If you are just responding with a 404 and no explanation, replace NotFoundObjectResult with a NotFoundResult.

And finally InternalServerErrors:

var result = (ObjectResult) await _controller.Get(id);
result.StatusCode.Should().Be(StatusCodes.Status500InternalServerError);

You can use integrationFixture for that using this NuGet package. This is an AutoFixture alternative for integration tests.

The documented examples use Get calls but you can do other calls too. Logically, you should test for the status code (OkObjectResult means 200) value and the response (which could be an empty string, that is no problem at all).

Here is the documented example for a normal Get call.

[Fact]
public async Task GetTest()
{
    // arrange
    using (var fixture = new Fixture<Startup>())
    {
        using (var mockServer = fixture.FreezeServer("Google"))
        {
            SetupStableServer(mockServer, "Response");
            var controller = fixture.Create<SearchEngineController>();

            // act
            var response = await controller.GetNumberOfCharacters("Hoi");

            // assert
            var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
            Assert.Contains("Hoi", request.RawQuery);
            Assert.Equal(8, ((OkObjectResult)response.Result).Value);
        }
    }
}

private void SetupStableServer(FluentMockServer fluentMockServer, string response)
{
    fluentMockServer.Given(Request.Create().UsingGet())
        .RespondWith(Response.Create().WithBody(response, encoding: Encoding.UTF8)
            .WithStatusCode(HttpStatusCode.OK));
}

In the example above, the controller is resolved using the DI described in your Startup class.

You can also do an actual REST call using using Refit. The application is self hosted inside your test.

using (var fixture = new RefitFixture<Startup, ISearchEngine>(RestService.For<ISearchEngine>))
{
    using (var mockServer = fixture.FreezeServer("Google"))
    {
        SetupStableServer(mockServer, "Response");
        var refitClient = fixture.GetRefitClient();
        var response = await refitClient.GetNumberOfCharacters("Hoi");
        await response.EnsureSuccessStatusCodeAsync();
        var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single();
        Assert.Contains("Hoi", request.RawQuery);
    }
}
Related