Имитация Entity Framework 6 ObjectResult с помощью Moq

Как я могу имитировать Entity Framework 6 ObjectResult с помощью Moq, чтобы я мог выполнять модульное тестирование своего кода, который зависит от подключения к базе данных EF?

Прочитав множество вопросов и ответов в этом направлении и почерпнув много самородков из того, что я прочитал, я реализовал то, что я считаю достаточно элегантным решением, и почувствовал, что должен поделиться им, поскольку сообщество здесь помогло мне добраться туда. Таким образом, я перейду к ответу на этот вопрос и потенциально открою себя для некоторой насмешки (каламбур):


person Jim Speaker    schedule 28.08.2015    source источник


Ответы (2)


Во-первых, у ObjectResult нет общедоступного конструктора без параметров, поэтому сначала необходимо создать тестируемую оболочку для ObjectResult. Ответ @forsvarir (https://stackoverflow.com/users/592182/forsvarir) в этом посте заставил меня задуматься правильно в этих строках (EF6 - не может имитировать возвращаемое значение для ObjectResult‹T› для модульного теста):

using System.Data.Entity.Core.Objects;

namespace MyNamespace.Mocks
{
    public class TestableEfObjectResult<T> : ObjectResult<T> { }
}

Конечно, DbContext нужно издеваться. Затем необходимо настроить ваш метод для возврата соответствующего имитированного перечислителя. Для удобства я создал метод, помогающий в создании фиктивных результатов EF, чтобы мой тестовый код не был загроможденным и избыточным. Это может жить в каком-то утилитарном классе, который у вас есть для ваших тестов, хотя я просто включил его здесь как частный метод. Ключевым моментом здесь является то, что результат фиктивного объекта должен возвращать перечислитель при вызове GetEnumerator:

namespace MyNamespace.Mocks
{
    public class MockSomeDbEntities
    {
        public static Mock<SomeDbEntities> Default
        {
            get
            {
                var mockSomeDbEntities = new Mock<SomeDbEntities>();

                mockSomeDbEntities
                  .Setup(e => e.SomeMethod(It.IsAny<int>()))
                  .Returns(MockEfResult(Enumerators.SomeCollection).Object);

                return mockSomeDbEntities;
            }
        }

        private static Mock<TestableEfObjectResult<T>> MockEfResult<T>(Func<IEnumerator<T>> enumerator) where T : class 
        {
            var mock = new Mock<TestableEfObjectResult<T>>();
            mock.Setup(m => m.GetEnumerator()).Returns(enumerator);
            return mock;
        }
    }
}

Класс Enumerators, который я создал для передачи перечислителя всякий раз, когда функция вызывается на макете, просто выглядит следующим образом. В этом примере у меня есть поддельный счетчик, создающий 5 строк данных:

using System;
using System.Collections.Generic;

namespace MyNamespace.FakeData
{
    public static class Enumerators
    {
        public static IEnumerator<Some_Result> SomeCollection()
        {
            yield return FakeSomeResult.Create(1);
            yield return FakeSomeResult.Create(2);
            yield return FakeSomeResult.Create(3);
            yield return FakeSomeResult.Create(4);
            yield return FakeSomeResult.Create(5);
        }
    }
}

И, как видите, это просто полагается на класс, который создает каждую фальшивую строку данных:

namespace MyNamespace.FakeData
{
    public static class FakeSomeResult
    {
        public static Some_Result Create(int id)
        {
            return new Some_Result
            {
                Id = id,
            };
        }
    }
}

Возможность имитировать на этом уровне действительно позволяет мне выполнять BDD и только имитировать или подделывать периферийные устройства, никогда не имитируя и не подделывая мой код, поэтому я получаю полное (r) тестовое покрытие.

Надеюсь, это поможет тем, кто, как и я, искал хороший и чистый способ издеваться над Entity Framework 6.

person Jim Speaker    schedule 28.08.2015

Я сталкивался с этой проблемой много раз

Мое решение состоит в том, чтобы издеваться над методом GetEnumeartor ObjectResult. Это можно легко сделать с помощью тестового метода с очень небольшими накладными расходами.

Например

скажем, у нас есть метод репозитория, который вызывает метод DBContext, возвращающий ObjectResult<string>. Метод repo получит результирующее значение string, перечислив ObjectResult<string>, возможно, с objResult.FirstOrDefault(). Как вы знаете, метод LINQ просто вызывает перечислитель ObjectResult. Таким образом, издевательство над функцией GetEnumeartor() функции ObjectResult поможет. Любое перечисление результата вернет наш имитированный перечислитель.

Пример


Вот простая простая функция, которая создает IEnumerator<string> из string. Это может быть встроено с помощью анонимного метода, но это затрудняет чтение здесь для иллюстрации.

var f = new Func< string,IEnumerator< string> >( s => ( ( IEnumerable< string > )new []{ s } ).GetEnumerator( ) );

эту функцию можно вызвать, передав такую ​​строку

var myObjResultReturnEnum = f( "some result" );

это может упростить получение IEnumerator<string>, которое мы ожидаем от метода GetEnumerator() ObjectResult<string>, поэтому любой метод репо, который вызывает ObjectResult, получит нашу строку

// arrange
const string shouldBe = "Hello World!";
var objectResultMock = new Mock<ObjectResult<string>>();
objectResultMock.Setup( or => or.GetEnumerator() ).Returns(() => myObjResultReturnEnum );

Нет, у нас есть фиктивный ObjectResult<string>, который мы можем передать в метод репо, предполагая, что наш метод в конечном итоге каким-то образом вызовет перечислитель и получит наше значение shouldBe.

var contextMock = new Mock<SampleContext>( );
contextMock.Setup( c => c.MockCall( ) ).Returns( ( ) => objectResultMock.Object );

// act
var repo = new SampleRepository( contextMock.Object );
var result = repo.SampleSomeCall( );

// assert
Assert.IsNotNull(result);
Assert.AreEqual(shouldBe, result);
person SimperT    schedule 16.12.2017
comment
Хороший. надо будет попробовать такой стиль. - person Jim Speaker; 18.12.2017