如何对将 Action 转换为 Func 的扩展方法进行单元测试?

How to unit test an extension method converting an Action to a Func?

我正在编写一个小型 C# 函数库,其中定义了一个 Unit `type 以及一堆要转换的扩展方法:

收件人:

public static partial class ActionExtensions
{
    public static Func<Unit> ToFunc(this Action action) =>
    () =>
    {
        action();
        return Unit.Default;
    };

    public static Func<T, Unit> ToFunc<T>(this Action<T> action) =>
        t =>
        {
            action(t);
            return Unit.Default;
        };

    public static Func<T1, T2, Unit> ToFunc<T1, T2>(this Action<T1, T2> action) =>
        (t1, t2) =>
        {
            action(t1, t2);
            return Unit.Default;
        };

     // other methods...

Unit定义如下:

public readonly struct Unit : IEquatable<Unit>
{
    public static readonly Unit Default = new Unit();

    public override int GetHashCode() => 
        0;

    public override bool Equals(object obj) =>
        obj is Unit;

    public bool Equals(Unit other) =>
        true;

    public static bool operator ==(Unit left, Unit right) =>
        true;

    public static bool operator !=(Unit left, Unit right) =>
        false;
}

我想知道如何对这些扩展方法进行单元测试。

我是否应该执行某种反射来分析 return 类型以及 Func 的参数,我是否应该检查在调用 Func 时调用底层 Action方法?

有什么想法吗?

最简单的方法是传递一个在调用操作时将布尔值设置为 true 的操作,然后测试扩展方法的结果。这证明了预期的行为,即

  • 动作被调用
  • 函数以默认单位返回

例如,

[Test] public void ToFuncForGivenActionInvokesAction()
{
    // arrange
    bool invoked = false;
    Action action = () => invoked = true;

    // act
    var func = action.ToFunc();

    // assert
    func().Should().Be(Unit.Default);
    invoked.Should().BeTrue();
}

在上面

  • 我正在使用 Fluent Assertions 来断言证据,但你当然可以使用任何你想要的
  • 不必费心检查 func 是否为空。测试 func() 的结果涵盖了这种情况。减少噪音。
  • 对带参数的操作重复。

我会跳过所有的反思。我怀疑被测方法的数量足够少,以至于可以重复一些代码,因为这比反射所需的扭曲更具可读性。

使用像 NSubstitute 这样的模拟框架,您可以轻松断言给定的 method/action 已被调用,并且传递的参数的值符合预期。

在下面的示例中,我使用的是 xUnit 测试框架。

断言 testDummy.Received().Run("foobar", 123); 如果

将无法通过测试
  1. Run方法还没有执行
  2. Run 方法会收到除 foobar123.
  3. 之外的其他参数值

public interface ITestDummy
{
    void Run(String arg1, Int32 arg2);
}

public class UnitTest
{
    [Fact]
    public void Test1()
    {   
        var testDummy = Substitute.For<ITestDummy>();

        Action<String, Int32> action = testDummy.Run;  
        Func<String, Int32, Unit> func = action.ToFunc();

        var result = func("foobar", 123);

        testDummy.Received().Run("foobar", 123);
        Assert.Equal(result, Unit.Default);
    }
}