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

时间:2019-05-08 22:05:35

标签: c# unit-testing .net-core delegates extension-methods

我正在编写一个小的Functional C#库,在其中定义了Unit`类型以及一堆扩展方法以进行转换:

  • Action
  • Action<T1>
  • Func<T1, T2>
  • 等等

收件人:

  • Func<Unit>
  • Func<T1, Unit>
  • Func<T1,T2, Unit>
  • 等等。
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;
}

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

我应该进行某种类型的反射来分析Func的返回类型和参数,是否应该在调用Func时检查是否调用了基础Action方法?

有什么想法吗?

2 个答案:

答案 0 :(得分:3)

最简单的方法是传递一个动作,该动作在调用该动作时将布尔值设置为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()的结果可以解决这种情况。噪音更少。
  • 重复执行带参数的操作。

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

答案 1 :(得分:2)

使用类似NSubstitute的模拟框架,您可以轻松断言已调用了给定的方法/操作,并且所传递的参数的值符合预期。

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

如果

,则断言testDummy.Received().Run("foobar", 123);将通过测试
  1. Run方法尚未执行
  2. Run方法将收到除foobar123以外的其他参数值。

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);
    }
}