UnitTestEx icon indicating copy to clipboard operation
UnitTestEx copied to clipboard

HttpTriggerTester Expression Tree Limitations

Open m-flak opened this issue 2 years ago • 3 comments

HttpTriggerTester Expression Tree Limitations

Background

In my tests project, I have created an extension method using reflection to automatically get the route of my HTTP-Triggered-Functions with that method. The goal here was to eliminate requirements for test code updates after changes to a Function's route in the main code. It almost works.

I've since written my code around this limitation, but it requires me to spin up & run an entire HostTesterBase implementor twice. 😰

Issue

HttpTriggerTester's Run/RunAsync methods, unlike TypeTester's, uses expression trees. SRC

public async Task<ActionResultAssertor> RunAsync(Expression<Func<TFunction, Task<IActionResult>>> expression)

This means that I am unable to write something like this without experiencing compilation errors:

        var sut = await test
            .HttpTrigger<QueueTaskCreateFunction>()
            .RunAsync(f => f.Run(test.CreateHttpRequest(HttpMethod.Post, f.GetFunctionRoute(), contentType: MediaTypeNames.Text.Plain), context.Object));

A statement like this would work with TypeTester, but I can't use TypeTester to test my HTTP-Triggered-Function.

Is there a technical reason for HttpTriggerTester's Run/RunAsync methods not having an overload for Action<T> or Func<T1,T2>??

Workaround

The following boilerplate accomplishes what I intended without any issues:

       var route = test
            .Type<QueueTaskCreateFunction>()
            .Run(f => f.GetFunctionRoute())
            .Result;

        var request = test
            .CreateHttpRequest(HttpMethod.Post, route, contentType: MediaTypeNames.Text.Plain);

        var sut = await test
            .HttpTrigger<QueueTaskCreateFunction>()
            .RunAsync(f => f.Run(request, context.Object));

m-flak avatar Feb 27 '24 23:02 m-flak

Hi @m-flak,

The expression usage is by design as this provides an opportunity for UnitTestEx to verify/assert that the method being invoked as HTTP, i.e. has the HttpTriggerAttribute. And, that the corresponding HTTP method is expected.

A backlog item is for this to further inspect the Route and verify what is passed matches accordingly. Today, this is accepted as-is and technically anything can be passed in as the route content.

I am not sure that a test should infer a value in the code being tested and then be used in the actual test. The test should assert based on intent; otherwise, how do you know whether there is an issue, e.g. maybe a misspelling in the route?

How often do the routes change that this is an issue?

Thanks...

chullybun avatar Feb 29 '24 19:02 chullybun

I am not sure that a test should infer a value in the code being tested and then be used in the actual test. The test should assert based on intent; otherwise, how do you know whether there is an issue, e.g. maybe a misspelling in the route?

How often do the routes change that this is an issue?

Okay, all I wanted to do was get the route value automatically from the attribute and use it with CreateHttpRequest. There's no way to do that currently, so I attempted to implement that on my end.

A backlog item is for this to further inspect the Route [...]

This is the gap I tried to fill that led me to this issue. I wanted the ability to use the metadata within HttpTriggerAttribute to automatically create test http requests with the correct route.

m-flak avatar Mar 03 '24 18:03 m-flak

I rethought my approach and made a simple utility method. Still, it would be cool if UnitTestEx could do this for me.

    public static string GetHttpRoute(Type functionClass)
    {
        string? route = null;

        var runMethod = functionClass.GetMethod("Run");
        var runParams = runMethod?.GetParameters();
        if (runMethod is null
            || runParams is null
            || runParams.Length == 0)
        {
            return string.Empty;
        }

        var attribute = runParams[0]
            .GetCustomAttributes(true)
            .FirstOrDefault(
                a => typeof(HttpTriggerAttribute).IsInstanceOfType(a),
                null)
            as HttpTriggerAttribute;

        route = attribute?.Route;
        route ??= string.Empty;
        return route;
    }

m-flak avatar Mar 06 '24 03:03 m-flak