Peaeater
Peaeater

Reputation: 636

Asp.Net Core 2.1 ApiController does not automatically validate model under unit test

I am using xUnit to unit test an ASP.NET Core 2.1 controller method that returns an ActionResult<T>. The controller class is decorated with the [ApiController] attribute so that the method, when running live, performs model validation automatically. However, the controller method does not automatically fire model validation in my unit test, and I can't figure out why.

Here is my Unit Test.

[Fact]
public async Task post_returns_400_when_model_is_invalid()
{
    // arrange
    var mockHttpClientFactory = new Mock<IHttpClientFactory>();
    var mockHttpMessageHandler = new Mock<FakeHttpMessageHandler> {CallBase = true};
    mockHttpMessageHandler.Setup(mock => mock.Send(It.IsAny<HttpRequestMessage>()))
        .Returns(new HttpResponseMessage(HttpStatusCode.Accepted)
        {
            Content = new StringContent("{\"response\": {\"numFound\": 0, \"start\": 0, \"docs\": []}}")
        });
    var httpClient =
        new HttpClient(mockHttpMessageHandler.Object)
        {
            BaseAddress = new Uri("http://localhost:8983/"),
            DefaultRequestHeaders = {{"Accept", "application/json"}}
        };
    mockHttpClientFactory.Setup(mock => mock.CreateClient("SolrApi")).Returns(httpClient);
    var slackOptions = Options.Create<SlackOptions>(new SlackOptions());
    var prdSolrService = new PrdSolrService(Options.Create<PrdOptions>(new PrdOptions()));
    var slackMessageService = new PrdSlackMessageService(new HtmlTransformService(), slackOptions);
    var controller = new SlackPrdController(slackOptions, mockHttpClientFactory.Object, prdSolrService, slackMessageService);
    var slackRequest = new SlackRequest();

    // act
    var sut = await controller.Post(slackRequest);

    // assert 
    // note: validation should fail and not return a SlackMessage, but validation is not firing
    // auto-validation is part of [ApiController] attribute on the Controller class
    Assert.IsType<BadRequestObjectResult>(sut.Result);
}

Here is the Controller method being tested.

[HttpPost]
public async Task<ActionResult<SlackMessage>> Post(SlackRequest request)
{
    if (request.Token != _slackOptions.Token)
    {
        ModelState.AddModelError("Token", "Invalid verification token.");
        return BadRequest(ModelState);
    }

    var solrUri = _solrService.SlackRequestToSolrUri(request);
    var client = _httpClientFactory.CreateClient("SolrApi");
    var raw = await client.GetStringAsync(solrUri);
    var response = _solrService.ParseSolrResponse(raw);
    var slackMessage = _slackMessageService.InitialMessage(response);

    return slackMessage;
}

Here is the SlackRequest model, where the Token property is required.

public class SlackRequest
{
    public SlackRequest() {}

    [JsonProperty("token")]
    [Required]
    public string Token { get; set; }

    [JsonProperty("team_id")]
    public string TeamId { get; set; }

    [JsonProperty("team_domain")]
    public string TeamDomain { get;set; }

    [JsonProperty("enterprise_id")]
    public string EnterpriseId { get; set; }

    [JsonProperty("enterprise_name")]
    public string EnterpriseName { get; set; }

    [JsonProperty("channel_id")]
    public string ChannelId { get; set; }

    [JsonProperty("channel_name")]
    public string ChannelName { get; set; }

    [JsonProperty("user_id")]
    public string UserId { get; set; }

    [JsonProperty("user_name")]
    public string UserName { get; set; }

    [JsonProperty("command")]
    public string Command { get;set; }

    [JsonProperty("text")]
    public string Text { get; set; }

    [Url]
    [JsonProperty("response_url")]
    public string ResponseUrl { get; set; }

    [JsonProperty("trigger_id")]
    public string TriggerId { get; set; }
}

Upvotes: 3

Views: 911

Answers (1)

Nkosi
Nkosi

Reputation: 247123

The controller class is decorated with the [ApiController] attribute so that the method, when run live, performs model validation automatically. However, the controller method does not automatically fire model validation in my unit test, and I can't figure out why.

The ApiControllerAttribute is metadata that is only relevant at run time by the infrastructure, so that means you will have to use TestServer in an integration test and actually request the action under test for it to be part of the test and recognized by the framework.

This is because as the request goes through the infrastructure, the attribute basically tags the controller/action to let an action filter (ModelStateInvalidFilter) inspect the model and update the model state of the request. If model is found to be invalid it will short circuit the request so that is does not even reach the action to be invoked.

Upvotes: 3

Related Questions