NSubstitute DbSet / IQueryable

ainsi, le cadre 6 est beaucoup mieux testable que les versions précédentes. Et il y a quelques beaux exemples sur internet pour des cadres comme Moq, mais le cas est, je préfère utiliser NSubstitute. J'ai fait traduire les exemples de" non-requête " pour qu'ils fonctionnent avec L'utilisation de NSubstitute, mais je n'arrive pas à me faire à l'idée du 'query-test'.

Comment Moq items.As<IQueryable<T>>().Setup(m => m.Provider).Returns(data.Provider); traduire à NSubstitute? J'ai pensé à quelque chose comme ((IQueryable<T>) items).Provider.Returns(data.Provider); mais cela ne fonctionne pas. J'ai aussi essayé items.AsQueryable().Provider.Returns(data.Provider); mais qui n'a pas fonctionné non plus.

L'exeption que j'obtiens est:

"le Système de.Exception NotImplementedException: le membre " IQueryable.Fournisseur n'a pas été implémenté sur le type 'DbSet1Proxy' which inherits from 'DbSet1'. Les doubles d'essai pour "DbSet" 1 " doivent fournir des implémentations de des méthodes et des propriétés qui sont utilisées."

permettez-moi donc de citer l'exemple de code du lien ci-dessus. Cet échantillon de code utilise Moq pour simuler le DbContext et le 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);

  // ...
}

Et c'est jusqu'où je suis venu avec NSubstitute!--9-->

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

  // ...
}

donc la question Est: Comment remplacer une propriété D'IQueryable (comme Provider)?

32
demandé sur s.meijer 2014-01-12 04:57:11

5 réponses

cela se produit à cause de la syntaxe spécifique de NSubstitute. Par exemple:

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

NSubstitute appelle le getter du fournisseur, puis il spécifie la valeur de retour. Cet appel de getter n'est pas intercepté par le remplaçant et vous avez une exception. Cela se produit en raison de la mise en œuvre explicite D'IQueryable.Fournisseur de biens en classe DbQuery.

vous pouvez explicitement créer des substituts pour plusieurs interfaces avec NSub, et il crée un proxy qui couvre tous les interfaces spécifiées. Les appels vers les interfaces seront alors interceptés par le remplaçant. Veuillez utiliser la syntaxe suivante:

// 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);
...
34
répondu Alexandr Nikitin 2014-01-12 15:01:56

grâce à Kevin, j'ai trouvé le problème dans ma traduction de code.

unittest exemples de code se DbSet, mais NSubstitute nécessite l'implémentation de l'interface. Donc l'équivalent de Moqs new Mock<DbSet<Blog>>() pour NSubstitute est Substitute.For<IDbSet<Blog>>(). Vous n'êtes pas toujours tenu de fournir l'Interface, c'est pourquoi j'étais confus. Mais dans ce cas précis, il s'est avéré crucial.

il s'est aussi avéré que nous n'avons pas à lancer à Queryable quand en utilisant L'interface IDbSet.

donc le code de test de travail:

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 ...
}

j'ai écrit une petite méthode d'extension pour nettoyer la section arranger des tests unitaires.

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

ce n'est Pas la question, mais dans le cas vous devez également être en mesure de soutenir des opérations asynchrones:

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);
15
répondu s.meijer 2014-01-12 15:55:28

c'est ma méthode statique générique de génération de faux DbSet. Il peut, par utile.

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

}
4
répondu Krzysztof Bronowski 2015-08-27 11:25:11

j'ai écrit un wrapper sur un an autour du même code sont de référencement de Test avec Votre Propre Test en Double (EF6 partir). Cette enveloppe peut être trouvée sur GitHub DbContextMockForUnitTests. Le but de ce wrapper est de réduire la quantité de code répétitif/dupliqué nécessaire pour configurer les tests unitaires qui font usage de EF où vous voulez vous moquer de cela DbContext et DbSets. La plupart du code Fe simulé que vous avez dans L'OP peut être réduit à 2 lignes de code (et seulement 1 si vous utilisez DbContext.Set<T> au lieu des propriétés DbSet) et le code simulé est alors appelé dans le wrapper.

Pour l'utiliser copier et d'inclure les fichiers dans le dossier MockHelpers à votre projet D'essai.

voici un exemple de test en utilisant ce que vous aviez ci-dessus, notez qu'il n'y a maintenant que 2 lignes de code nécessaires pour configurer le mock DbSet<T> sur la moqué 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
}

Il est tout aussi facile de faire ce test qui appelle quelque chose qui utilise le motif async/wait comme .ToListAsync() sur le 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
}
2
répondu Igor 2016-09-14 11:33:12

Vous ne devriez pas avoir besoin de vous moquer de tous les morceaux de L'IQueryable. Quand J'utilise NSubstitute pour me moquer D'un DbContext EF, je fais quelque chose comme ça:

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

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

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

avec une simple implémentation D'IDbSet autour d'une liste ou quelque chose pour my MockDbSet().

en général, vous devez vous moquer des interfaces, pas des types car NSubstitute ne fera que surcharger les méthodes virtuelles.

0
répondu Kevin 2014-01-12 01:15:19