2014-01-12 19 views
29

Так что EntityFramework 6 намного лучше тестируется, чем предыдущие версии. И есть some nice examples в Интернете для фреймворков, таких как Moq, но дело в том, что я предпочитаю использовать NSubstitute. У меня есть примеры «без запроса», переведенные для работы с использованием NSubstitute, но я не могу обойти «запрос-тест».NSubstitute DbSet/IQueryable <T>

Как Moq's items.As<IQueryable<T>>().Setup(m => m.Provider).Returns(data.Provider); перевести в NSubstitute? Я думал что-то вроде ((IQueryable<T>) items).Provider.Returns(data.Provider);, но это не сработало. Я также пробовал items.AsQueryable().Provider.Returns(data.Provider);, но это тоже не сработало.

Exeption Я получаю:

«System.NotImplementedException: Член 'IQueryable.Provider' не был реализован по типу 'DbSet 1Proxy' which inherits from 'DbSet 1' Test удваивается«DbSet`1. 'должен обеспечивать реализацию применяемых методов и свойств ".

Итак, позвольте мне привести пример кода из приведенной выше ссылки. В этом примере кода используется Moq для подделки DbContext и DbSet.

public void GetAllBlogs_orders_by_name() 
{ 
    // Arrange 
    var data = new List<Blog> 
    { 
    new Blog { Name = "BBB" }, 
    new Blog { Name = "ZZZ" }, 
    new Blog { Name = "AAA" }, 
    }.AsQueryable(); 

    var mockSet = new Mock<DbSet<Blog>>(); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType); 
    mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator()); 

    var mockContext = new Mock<BloggingContext>(); 
    mockContext.Setup(c => c.Blogs).Returns(mockSet.Object); 

    // ... 
} 

И это, как далеко я пришел с NSubstitute

public void GetAllBlogs_orders_by_name() 
{ 
    // Arrange 
    var data = new List<Blog> 
    { 
    new Blog { Name = "BBB" }, 
    new Blog { Name = "ZZZ" }, 
    new Blog { Name = "AAA" }, 
    }.AsQueryable(); 

    var mockSet = Substitute.For<DbSet<Blog>>(); 
    // it's the next four lines I don't get to work 
    ((IQueryable<Blog>) mockSet).Provider.Returns(data.Provider); 
    ((IQueryable<Blog>) mockSet).Expression.Returns(data.Expression); 
    ((IQueryable<Blog>) mockSet).ElementType.Returns(data.ElementType); 
    ((IQueryable<Blog>) mockSet).GetEnumerator().Returns(data.GetEnumerator()); 

    var mockContext = Substitute.For<BloggingContext>(); 
    mockContext.Blogs.Returns(mockSet); 

    // ... 
} 

Таким образом, вопрос; Как заменить свойство IQueryable (например, поставщика)?

+0

ОБНОВЛЕНО: Используйте пакет [EntityFramework.Testing.NSubstitute] (https://github.com/scott-xu/EntityFramework.Testing), что обеспечивает реализацию DbAsyncQueryProvider. –

ответ

34

Это происходит из-за NSubstitute синтаксиса конкретного.Например, в:

((IQueryable<Blog>) mockSet).Provider.Returns(data.Provider); 

NSubstitute называет получателя провайдера, а затем указывает возвращаемое значение. Этот getter-вызов не перехвачен заменой, и вы получаете исключение. Это происходит из-за явной реализации свойства IQueryable.Provider в классе DbQuery.

Вы можете явно создавать замены для нескольких интерфейсов с помощью NSub и создавать прокси-сервер, который охватывает все указанные интерфейсы. Затем вызовы на интерфейсы будут перехвачены заменой. Пожалуйста, используйте следующий синтаксис:

// Create a substitute for DbSet and IQueryable types: 
var mockSet = Substitute.For<DbSet<Blog>, IQueryable<Blog>>(); 

// And then as you do: 
((IQueryable<Blog>)mockSet).Provider.Returns(data.Provider); 
... 
+0

Спасибо! Это действительно полезная информация. Я еще не уверен, что это лучший способ заменить DbSet (как выяснилось, с помощью IDbSet решает проблемы), но он уверен в других ситуациях. –

+0

Я отмечаю вас как ответ, потому что ваше предложение является решением для поддержки Async (Substitute.For , IDbAsyncEnumerable >()). На самом деле это не проблема заданного вопроса, но она заключается в ее расширении. Еще раз спасибо за это понимание! –

+4

Совет: убедитесь, что ваш 'DbSet' в вашем контексте 'virtual' –

0

Вам не нужно издеваться над всеми частями IQueryable. Когда я использую NSubstitute для насмешливого ЭФ DbContext я сделать что-то вроде этого:

interface IContext 
{ 
    IDbSet<Foo> Foos { get; set; } 
} 

var context = Substitute.For<IContext>(); 

context.Foos.Returns(new MockDbSet<Foo>()); 

С простой реализацией IDbSet вокруг списка или что-то для моего MockDbSet().

В общем, вы должны быть издевательскими интерфейсами, а не типами, поскольку NSubstitute будет только переопределять виртуальные методы.

+0

В EF 6 Свойства DbSet должны быть виртуальными для проверки. Я не хочу создавать фальшивую обертку DbSet. Подстановка этих 4 свойств должна быть проще и лучше, чем создание обертки, которую вы предлагаете. Поэтому ваше предложение - это не тот ответ, который я ищу. –

+1

Судя по сообщению об ошибке, я бы сказал, что прокси-сервер DbSet, созданный NSubstitute, не поддерживает явно реализованный интерфейс, а только конкретный контракт или объект, который вы заменяете. Это проявляется в (https://github.com/nsubstitute/NSubstitute/issues/95) и может быть ограничением рамки. – Kevin

15

Благодаря Кевину, я нашел проблему в моем переводе кода.

unittest code samples насмешливо DbSet, но NSubstitute требует реализации интерфейса. Таким образом, эквивалент Moqs new Mock<DbSet<Blog>>() для NSubstitute - Substitute.For<IDbSet<Blog>>(). Вы не всегда обязаны предоставлять интерфейс, поэтому я смутился. Но в этом конкретном случае это оказалось решающим.

Также выяснилось, что при использовании интерфейса IDbSet нам не нужно передавать Queryable.

Так код Дрессировка:

public void GetAllBlogs_orders_by_name() 
{ 
    // Arrange 
    var data = new List<Blog> 
    { 
    new Blog { Name = "BBB" }, 
    new Blog { Name = "ZZZ" }, 
    new Blog { Name = "AAA" }, 
    }.AsQueryable(); 

    var mockSet = Substitute.For<IDbSet<Blog>>(); 
    mockSet.Provider.Returns(data.Provider); 
    mockSet.Expression.Returns(data.Expression); 
    mockSet.ElementType.Returns(data.ElementType); 
    mockSet.GetEnumerator().Returns(data.GetEnumerator()); 

    var mockContext = Substitute.For<BloggingContext>(); 
    mockContext.Blogs.Returns(mockSet); 

    // Act and Assert ... 
} 

Я написал небольшой метод Удлинитель для очистки секции Arrange единичных испытаний.

public static class ExtentionMethods 
{ 
    public static IDbSet<T> Initialize<T>(this IDbSet<T> dbSet, IQueryable<T> data) where T : class 
    { 
     dbSet.Provider.Returns(data.Provider); 
     dbSet.Expression.Returns(data.Expression); 
     dbSet.ElementType.Returns(data.ElementType); 
     dbSet.GetEnumerator().Returns(data.GetEnumerator()); 
     return dbSet; 
    } 
} 

// usage like: 
var mockSet = Substitute.For<IDbSet<Blog>>().Initialize(data); 

Не вопрос, но в случае, если вы также должны быть в состоянии поддерживать операции асинхронные:

public static IDbSet<T> Initialize<T>(this IDbSet<T> dbSet, IQueryable<T> data) where T : class 
{ 
    dbSet.Provider.Returns(data.Provider); 
    dbSet.Expression.Returns(data.Expression); 
    dbSet.ElementType.Returns(data.ElementType); 
    dbSet.GetEnumerator().Returns(data.GetEnumerator()); 

    if (dbSet is IDbAsyncEnumerable) 
    { 
    ((IDbAsyncEnumerable<T>) dbSet).GetAsyncEnumerator() 
     .Returns(new TestDbAsyncEnumerator<T>(data.GetEnumerator())); 
    dbSet.Provider.Returns(new TestDbAsyncQueryProvider<T>(data.Provider)); 
    } 

    return dbSet; 
} 

// create substitution with async 
var mockSet = Substitute.For<IDbSet<Blog>, IDbAsyncEnumerable<Blog>>().Initialize(data); 
// create substitution without async 
var mockSet = Substitute.For<IDbSet<Blog>>().Initialize(data); 
+0

Это нормально. Но что такое TestDbAsyncEnumerator и TestDbAsyncQueryProvider. – cosset

+0

Это провайдер запросов inmemory db, как объясняется ссылкой, которую я предоставляю в своем вопросе, а также этот ответ. Взгляните на страницу msdn: http://msdn.microsoft.com/nl-nl/data/dn314429.aspx#async –

+2

Я пробовал это, но получаю исключение: NSubstitute.Exceptions.CouldNotSetReturnDueToTypeMismatchException: Не удается вернуть значение введите IDbSet'1Proxy для BloggingContext.get_Blogs (ожидаемый тип DbSet'1). Контекст был сгенерирован шаблоном EF TT. – Shevek

4

Это мой статический общий статический метод для создания поддельной DbSet. Это может быть полезно.

public static class CustomTestUtils 
{ 
    public static DbSet<T> FakeDbSet<T>(List<T> data) where T : class 
    { 
     var _data = data.AsQueryable(); 
     var fakeDbSet = Substitute.For<DbSet<T>, IQueryable<T>>(); 
     ((IQueryable<T>)fakeDbSet).Provider.Returns(_data.Provider); 
     ((IQueryable<T>)fakeDbSet).Expression.Returns(_data.Expression); 
     ((IQueryable<T>)fakeDbSet).ElementType.Returns(_data.ElementType); 
     ((IQueryable<T>)fakeDbSet).GetEnumerator().Returns(_data.GetEnumerator()); 

     fakeDbSet.AsNoTracking().Returns(fakeDbSet); 

     return fakeDbSet; 
    } 

} 
+0

Я думаю, что отсутствует ключевое слово «это» из определения аргумента «данные», –

2

Я написал обертку около года назад примерно в том же код, который вы ссылаетесь от Testing with Your Own Test Doubles (EF6 onwards). Этот обертку можно найти по адресу GitHub DbContextMockForUnitTests. Цель этой оболочки - уменьшить количество повторяющегося/повторяющегося кода, необходимого для установки модульных тестов, которые используют EF, где вы хотите издеваться над этими DbContext и DbSets. Большая часть фальшивого кода EF, который у вас есть в OP, может быть уменьшена до двух строк кода (и только 1, если вы используете DbContext.Set<T> вместо свойств DbSet), а затем код-код вызывается в обертке.

Чтобы использовать его, скопируйте и включите файлы в папку MockHelpers в свой тестовый проект.

Вот пример теста с использованием того, что у вас было выше, обратите внимание, что в настоящее время для настройки макета DbSet<T> требуется только 2 строки кода на издеваемом DbContext.

public void GetAllBlogs_orders_by_name() 
{ 
    // Arrange 
    var data = new List<Blog> 
    { 
    new Blog { Name = "BBB" }, 
    new Blog { Name = "ZZZ" }, 
    new Blog { Name = "AAA" }, 
    }; 

    var mockContext = Substitute.For<BloggingContext>(); 

    // Create and assign the substituted DbSet 
    var mockSet = data.GenerateMockDbSet(); 
    mockContext.Blogs.Returns(mockSet); 

    // act 
} 

Это так же просто, чтобы сделать этот тест, который вызывает то, что использует асинхронный/ОЖИДАНИЕ рисунок как .ToListAsync() на DbSet<T>.

public async Task GetAllBlogs_orders_by_name() 
{ 
    // Arrange 
    var data = new List<Blog> 
    { 
    new Blog { Name = "BBB" }, 
    new Blog { Name = "ZZZ" }, 
    new Blog { Name = "AAA" }, 
    }; 

    var mockContext = Substitute.For<BloggingContext>(); 

    // Create and assign the substituted DbSet 
    var mockSet = data.GenerateMockDbSetForAsync(); // only change is the ForAsync version of the method 
    mockContext.Blogs.Returns(mockSet); 

    // act 
} 
Смежные вопросы