Макет списка динамических объектов C# в качестве возвращаемого значения

#c# #entity-framework #mocking

Вопрос:

У меня есть функция, которая возвращает числовой динамический объект

 public IEnumerable<dynamic> GetNameByRequestIds(List<long> requestIds) {
    return (
    from r in Session.All<Request>()
    where requestIds.Contains(r.REQ_ID)
    select new {
        ReqId = r.REQ_ID,
        Name = r.Name
    }
    ).AsEnumerable();
}
 

В модульном тесте я издеваюсь над этим,

 List<dynamic> myList = new List<dynamic>() {
    new {
        ReqId = 1,
        Name = "myName",
    }
};

_db.Setup(x => x.GetNameByRequestIds(It.IsAny<List<long>>())).Returns(myList);
 

Но когда я выполнил следующий код, он выдал исключение

Майкрософт.CSharp.привязка времени выполнения.Исключение RuntimeBinderException: «объект» не содержит определения для «ReqID»

 var dynamicList = _db.GetNameByRequestIds(myReqIds).ToList();
foreach (var r in dynamicList ) {
    Console.WriteLine(r.ReqId);
    Console.WriteLine(r.Name);
}
 

Это работает, когда я запрашиваю реальную базу данных, но не проходит модульный тест.

В режиме отладки

  • Динамический список, тип
 System.Collections.Generic.List<dynamic> {System.Collections.Generic.List<object>}
 

и r показывает

  • Тип <Anonymous Type>

Ответ №1:

Какую насмешливую структуру вы используете? Это несколько похоже на Moq, однако код для доступа к высмеянным результатам выглядит неполным. Я попробовал вышеописанное с Moq (v16.4), и это сработало, как и ожидалось.

Что не ясно в вашем примере, так это тип для _db. Сокращенный пример того, что работает:

 // The DB wrapper (i.e. you're real code to be mocked away)

public interface IData
{
    IEnumerable<dynamic> GetNameByRequestIds(IEnumerable<long> ids);
}

public class Data : IData
{
    public IEnumerable<dynamic> GetNameByRequestIds(IEnumerable<long> ids)
    {       
        return (
           from r in Session.All<Request>()
           where requestIds.Contains(r.REQ_ID)
           select new {
               ReqId = r.REQ_ID,
               Name = r.Name
           }
        ).AsEnumerable();
    }
}

// Then the code under test... (The service/controller you want to unit test...)

public class Consumer
{
    private readonly IData _db;

    public Consumer(IData db)
    {
       _db = db;
    }

    public void DoSomething(IEnumerable<long> ids)
    {
        foreach (var row in _db.GetNameByRequestIds(ids)
        {
            Console.WriteLine(row.ReqId);
            Console.WriteLine(row.Name);
            // Do whatever with the returned data rows...
        }
    }
}   

// Then the Test setup itself...

[Test]
public void TestConsumerDoesSomething()
{
    var mock = new Mock<IData>();
    mock.Setup(x => x.GetNameByRequestIds(It.IsAny<IEnumerable<long>>())).Returns(populate());
    var obj = new Consumer(mock.Object);
    obj.DoSomething();
}

private IEnumerable<dynamic> populate()
{
    return new List<dynamic>
    {
        new {ReqId = 1, Name = "myName"}
    };
}
 

Все это, по-видимому, работало так, как и следовало ожидать, с тестируемым методом («doSomething»), получающим доступ к данным, предоставленным Макетом, вместо «реального» поставщика БД и издевательского динамического объекта, предоставляющего идентификатор и имя.

Комментарии:

1. Я тоже чувствую себя так странно, что позже игнорирую динамику и создаю объект. Это все равно работает.