Skip to content

Asynchronous ExecutionTime #858

@ghost

Description

Description

This is, for all intents and purposes, a feature request.

I would like to use ExecutionTimeOf() (or, even better, ExecutionTime() directly) with Func<Task> in addition to the usual Action, so that I can easily check the behavior of time-sensitive awaitable methods.

Currently I have to circumvent this problem through the use of System.Diagnostics.Stopwatch, which I think is a suboptimal solution since it takes me out of the level of abstraction I would like to be at when composing unit tests - and even more so when composing scenarios.

Complete minimal example reproducing the issue

using System;
using System.Diagnostics;
using System.Threading.Tasks;

using FluentAssertions;
using FluentAssertions.Extensions;

using Xunit;

namespace Yet.Another.FeatureRequest
{
    public class ProofOfConcept
    {
        [Fact]
        public async Task TimingAnAsyncMethod_CurrentlyRequiresAStopWatch()
        {
            // ARRANGE
            var testee = new Testee(5.Seconds());
            var time = new Stopwatch();

            // ACT (more or less)
            Func<Task> action = async () => 
            {
                time.Start();
                await testee.SomeMethodAsync();
                time.Stop();
            };

            // ASSERT
            action.Should().NotThrow();
            time.Elapsed.Should().BeCloseTo(
                testee.Delay, 1.Seconds(),
                "I'm generous when it comes to async timing");
        }
    }

    public class Testee
    {
        public TimeSpan Delay { get; }

        public Testee(TimeSpan delay) => Delay = delay;

        public async Task SomeMethodAsync() => await Task.Delay(Delay);
    }
}

Expected behavior:

Several versions come to mind, such as

// ACT (or somesuch)
Func<Task> action = async () => await testee.SomeMethodAsync();

// ASSERT
action.Should().NotThrow();
action.ExecutionTime().Should().BeCloseTo(testee.Delay, 1.Seconds());
// which is obviously bad due to double execution...

or

// ACT (sort of)
Action action = () => testee.ExecutionTimeOf(async _ => await _.SomeMethodAsync())
    .Should().BeCloseTo(testee.Delay, 1.Seconds());

// ASSERT
action.Should().NotThrow();

or

// ACT (kinda)
Func<Task> action = async () => await testee.ExecutionTimeOf(_ => _.SomeMethodAsync())
    .Should().BeCloseTo(testee.Delay, 1.Seconds());

// ASSERT
action.Should().NotThrow();

Actual behavior:

  1. ExecutionTime is an extension Method of Action - so... no dice.
  2. Async lambda expressions cannot be converted to expression trees, apparently
  3. ExecutionTimeOf cannot handle Task or Task<T> differently and especially cannot provide an awaitable object

Versions

FluentAssertions v5.3.2
xunit v2.3.1
.Net Core v2.1.0

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions