かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Azure Functions v2 の HttpTrigger の関数を単体テストしたいね

端的にいうと HttpRequest クラスやら IActionResult クラスやらを引数や戻り値に持つもののテストです。 ここら辺を Moq を使ってモックしてみたいと思います。テスト対象は、HttpTrigger を使って作成したデフォルトの関数です。以下のような感じ。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace FunctionApp1
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]HttpRequest req, ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            return name != null
                ? (ActionResult)new OkObjectResult($"Hello, {name}")
                : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
        }
    }
}

GET の場合と POST の場合とで処理が分かれてるのと、正しいパラメータが渡ってこなかったときの 3 パターンくらいでしょうか。さくっと xUnit と Moq を導入したプロジェクトで、こんな感じのコードで行けました。

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace XUnitTestProject1
{
    public class UnitTest1
    {
        [Fact]
        public async Task GetPattern()
        {
            var httpRequestMock = new Mock<HttpRequest>();
            httpRequestMock.Setup(x => x.Query["name"]).Returns("Tanaka");
            httpRequestMock.Setup(x => x.Body).Returns(new MemoryStream());
            var response = await FunctionApp1.Function1.Run(httpRequestMock.Object, new Mock<ILogger>().Object);
            var okObjectResult = Assert.IsType<OkObjectResult>(response);
            Assert.Equal("Hello, Tanaka", okObjectResult.Value as string);
        }

        [Fact]
        public async Task PostPattern()
        {
            var httpRequestMock = new Mock<HttpRequest>();
            httpRequestMock.Setup(x => x.Query).Returns(new Mock<IQueryCollection>().Object);
            var body = new MemoryStream();
            var json = Encoding.UTF8.GetBytes("{ name: 'Tanaka' }");
            await body.WriteAsync(json, 0, json.Length);
            body.Seek(0, SeekOrigin.Begin);
            httpRequestMock.Setup(x => x.Body).Returns(body);

            var response = await FunctionApp1.Function1.Run(httpRequestMock.Object, new Mock<ILogger>().Object);
            var okObjectResult = Assert.IsType<OkObjectResult>(response);
            Assert.Equal("Hello, Tanaka", okObjectResult.Value as string);
        }

        [Fact]
        public async Task BadRequestPattern()
        {
            var httpRequestMock = new Mock<HttpRequest>();
            httpRequestMock.Setup(x => x.Query).Returns(new Mock<IQueryCollection>().Object);
            httpRequestMock.Setup(x => x.Body).Returns(new MemoryStream());

            var response = await FunctionApp1.Function1.Run(httpRequestMock.Object, new Mock<ILogger>().Object);
            var badRequestObjectResult = Assert.IsType<BadRequestObjectResult>(response);
            Assert.Equal("Please pass a name on the query string or in the request body", badRequestObjectResult.Value as string);
        }
    }
}

意外と簡単ですね。Moq は割と強い。