EF6 - 无法模拟ObjectResult的返回值<t>进行单元测试

时间:2015-06-26 18:10:12

标签: c# unit-testing mocking entity-framework-6 moq

我在我试图进行单元测试的方法中有类似的代码:

return _context.usp_get_Some_Data(someStringParam).FirstOrDefault();

存储过程调用返回类型:

ObjectResult<usp_get_Some_Data_Result>. 

在我的单元测试中,我尝试做这样的事情(使用NUnit和Moq):

var procResult = new ObjectResult<usp_get_Some_Data_Result>();
mockContext.Setup(m => m.usp_get_Some_Data(It.IsAny<string>()))
    .Returns(procResult);

但是,我无法创建ObjectResult的实例(这是System.Data.Entity.Core.Objects.ObjectResult&lt; T&gt;,而不是旧的System.Data.Objects实例)。它没有公共无参数构造函数,但documentation表示它有一个受保护的构造函数。从我的测试来看,他的文档似乎不正确。

我尝试过的事情: 我尝试创建一个派生类并在构造函数上调用base(),我也尝试使用反射(Activator.CreateInstance和使用NonPublic的BindingFlags调用ConstructorInfo,所有这些都失败了(它)从我的调试中看来,该类型确实有三个私有构造函数,所有这些构造函数都有3个或更多参数,但不幸的是,这似乎是一个主要的努力来弄清楚这些参数实际需要什么。)

我还试过创建一个IEnumberable&lt; usp_get_Some_Data_Result&gt;并将其转换为ObjectResult&lt; usp_get_Some_Data_Result&gt;但演员失败了。另外,我尝试过像

这样的东西
var mockObjectResult = new Mock<ObjectResult<usp_get_Some_Data_Result>>();

我所尝试的几乎所有内容都失败了,并且默认构造函数不可用时出现了类似的错误。

问题: 有没有办法创建ObjectResult&lt; T&gt;的实例?用于单元测试,还是我可以创建的任何其他类型可以成功地转换为ObjectResult&lt; T&gt;?

4 个答案:

答案 0 :(得分:7)

也许我错过了什么,但你不能这样做:

class TestableObjectResult<T> : ObjectResult<T>
{
}

然后在你的测试中:

var mockObjectResult = new Mock<TestableObjectResult<usp_get_Some_Data_Result>>();

MockObject确实有一个受保护的构造函数,你不需要做任何事情来调用它,因为它没有任何参数,当你构造testable时,自动连线会处理它版本,所以我不确定你的意思是&#34;在构造函数上调用base()&#34; ...

如果我右键单击ObjectResult并选择goto定义,文件顶部如下所示:

public class ObjectResult<T> : ObjectResult, IEnumerable<T>, IEnumerable, IDbAsyncEnumerable<T>, IDbAsyncEnumerable
{
    // Summary:
    //     This constructor is intended only for use when creating test doubles that
    //     will override members with mocked or faked behavior. Use of this constructor
    //     for other purposes may result in unexpected behavior including but not limited
    //     to throwing System.NullReferenceException.
    protected ObjectResult();

答案 1 :(得分:1)

如上所述,我正在添加此答案以涵盖创建枚举器,以便上面可以实际测试一些假数据:

在[TestFixture]类中,创建如下方法:

private static IEnumerator<usp_get_Some_Data_Result> GetSomeDataResultEnumerator()
{
    yield return FakeSomeDataResult.Create(1, true);
    yield return FakeSomeDataResult.Create(2, false);
}

如前一个答案中所提供的,这个方便的小包装类允许实例化ObjectResult:

public class TestableObjectResult<T> : ObjectResult<T> { }

如上一个答案所述,在[SetUp]方法中:

var mockObjectResult = new Mock<TestableObjectResult<usp_get_Some_Data_Result>>();

在创建新Mock之后,将其设置为返回Enumerator:

mockObjectResult.Setup(d => d.GetEnumerator()).Returns(GetSomeDataResultEnumerator());

现在,OP可以从mockContext返回一些假数据,而不会在尝试获取枚举数时抛出空引用异常:

mockContext.Setup(m => m.usp_get_Some_Data(It.IsAny<string>()))
    .Returns(mockObjectResult);

顺便说一句, 我只是使用帮助程序类来构造我的假数据,以消除冗余:

public static class FakeSomeDataResult
{
    public static usp_get_Some_Data_Result Create(int index)
    {
        return new usp_get_Some_Data_Result
        {
            SomeFriendlyNameProperty = string.Format("Some Data Result {0}", index),
        };
    } 
}

答案 2 :(得分:1)

正如@forsvarir所提到的,您可以创建一个TestableObjectResult类并覆盖GetEnumerator()以返回您想要的任何内容。

这样的事情:

private class TestableObjectResult : ObjectResult<Animal>
    {
        public override IEnumerator<Animal> GetEnumerator()
        {
            return new List<Animal>() { new Animal(), new Animal() }.GetEnumerator();
        }
    }

答案 3 :(得分:0)

这是我的解决方案。

到目前为止,它似乎对我有用。

public static class MoqExtentions
{
    public static void SetupReturn<T>(this Mock<ObjectResult<T>> mock, T whatToReturn)
    {
        IEnumerator<T> enumerator = ((IEnumerable<T>) new T[] {whatToReturn}).GetEnumerator();

        mock.Setup(or => or.GetEnumerator())
            .Returns(() => enumerator);
    }
}

现在,您可以模拟ObjectResult<T>并调用设置ObjectResult<T>的扩展方法,以返回传递给扩展方法的任何内容。然后可以将模拟的ObjectResult<T>传递给模拟的DbContext方法设置作为方法调用的返回值。如果需要,可以将其传递给访问ObjectResult<T>的模拟repo方法。

以下是使用模拟ObjectResult<string>

的测试示例
    public void MockObjectResultReturn_OfString_Test()
    {
        // arrange
        const string shouldBe = "Hello World!";
        var sut = new Mock<ObjectResult<string>>();

        // act
        sut.SetupReturn<string>(shouldBe);

        //assert
        Assert.IsNotNull(sut);
        Assert.IsNotNull(sut.Object);
        Assert.AreEqual(shouldBe, sut.Object?.FirstOrDefault());
    }