EF6 - Cannot Mock Return Value for ObjectResult<T> for Unit Test

十年热恋 提交于 2019-12-23 12:18:20

问题


I have code similar to this in a method that I'm trying to unit test:

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

The stored proc call return type:

ObjectResult<usp_get_Some_Data_Result>. 

In my unit test, I'm trying to do something like this (using NUnit and Moq):

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

However, I'm not able to create an instance of ObjectResult (this is System.Data.Entity.Core.Objects.ObjectResult<T>, not the older System.Data.Objects one). It doesn't have a public parameterless constructor, but the documentation says it has a protected one. From my testing, he documentation appears to be incorrect.

What I've Tried: I've tried creating a derived class and calling base() on the constructor, and I've also tried using reflection (both Activator.CreateInstance and invoking the ConstructorInfo with BindingFlags of NonPublic, all of which have failed (it appears from my debugging this that the type does have three private constructors, all of which have 3 or more parameters, but unfortunately it looks like a major effort to figure out what is actually required for these parameters).

I've also tried creating an IEnumberable<usp_get_Some_Data_Result> and casting it to ObjectResult<usp_get_Some_Data_Result> but the cast fails. In addition, I've tried something like

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

Pretty much everything I've tried fails with a similar error about a default constructor not being available.

Question: Is there any way to create an instance of ObjectResult<T> for unit testing, or is there any other type that I can create that can be successfully cast to ObjectResult<T>?


回答1:


Maybe I'm missing something, but can't you just do this:

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

And then in your test:

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

MockObject does have a protected constructor, you don't really have to do anything to call it, since it doesn't have any parameters, the auto-wiring will take care of it when you construct the testable version, so I'm not sure what you mean by you're "calling base() on the constructor"...

If I right click on ObjectResult and select goto definition the top of the file looks like this:

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



回答2:


As noted, I'm adding this answer to cover creating the Enumerator so the above can actually test some fake data:

Within the [TestFixture] class, create a method like the following:

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

As provided in the previous answer, this handy little wrapper class allows instantiating the ObjectResult:

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

As provided in the previous answer, within the [SetUp] method:

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

Following this new Mock creation, set it up to return the Enumerator:

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

Now the OP can return some fake data from the mockContext without it throwing a null reference exception when trying to get the enumerator:

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

BTW, I'm just using a helper class to construct my fake data, to eliminate redundancy:

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



回答3:


As @forsvarir mentioned, you can create a TestableObjectResult class and override the GetEnumerator() to return whatever you want.

Something like this:

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



回答4:


Here is my solution.

It has seemed to work for me so far.

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

Now you can mock a ObjectResult<T> and call the extention method that sets up the ObjectResult<T> to return whatever you pass into the extention method. Then the mocked ObjectResult<T> can be passed to a mocked DbContext method setup as the return value of the method call. And if needed, that can be passed to a mocked repo method that accessess the ObjectResult<T>.

Here is an test example of using the mocked 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());
    }


来源:https://stackoverflow.com/questions/31079558/ef6-cannot-mock-return-value-for-objectresultt-for-unit-test

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!