2016-09-11 3 views
0

je la configuration suivante:méthode Raillé DbSet lance NotImplementedException lorsqu'il est appelé à l'intérieur du contrôleur

DbContext:

public class ApplicationDbContext : IdentityDbContext<ApplicationUser> 
{ 
    public virtual DbSet<Album> Album { get; set; } 

    public ApplicationDbContext() 
     : base("DefaultConnection", throwIfV1Schema: false) 
    { 
    } 

    public static ApplicationDbContext Create() 
    { 
     return new ApplicationDbContext(); 
    } 
} 

Modèle:

public class Album 
{ 
    public int AlbumID { get; set; } 

    [StringLength(150)] 
    public string Title { get; set; } 
} 

Controller:

public class AlbumController : Controller 
{ 

    ApplicationDbContext db = new ApplicationDbContext(); 

    public AlbumController(ApplicationDbContext injectDb) 
    { 
     db = injectDb; 
    } 

    // POST: Albums/Delete/5 
    [HttpPost, ActionName("Delete")] 
    [ValidateAntiForgeryToken] 
    [Authorize(Roles = "Admin")] 
    public ActionResult DeleteConfirmed(int id) 
    { 
     Album album = db.Album.Find(id); 

     db.Album.Remove(album); 
     db.SaveChanges(); 
     return RedirectToAction("Index"); 
    } 
} 

J'ai écrit le test unitaire en utilisant Moq et xUnit pour vérifier la fonctionnalité DeleteConfirmed:

public class AlbumsControllerTests 
    { 
     public static Mock<DbSet<T>> MockDbSet<T>(List<T> inputDbSetContent) where T : class 
     { 
      var DbSetContent = inputDbSetContent.AsQueryable(); 
      var dbSet = new Mock<DbSet<T>>(); 

      dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(DbSetContent.Provider); 
      dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(DbSetContent.Expression); 
      dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(DbSetContent.ElementType); 
      dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => inputDbSetContent.GetEnumerator()); 
      dbSet.Setup(m => m.Add(It.IsAny<T>())).Callback<T>((s) => inputDbSetContent.Add(s)); 
      dbSet.Setup(m => m.Remove(It.IsAny<T>())).Callback<T>((s) => inputDbSetContent.Remove(s)); 
      return dbSet; 
     } 

     [Fact] 
     public void DeleteConfirmedTest() 
     { 
      // Arrange 
      var mockAlbumSet = MockDbSet(new List<Album> { }); 

      Mock<ApplicationDbContext> sutDbContext = new Mock<ApplicationDbContext>() { CallBase = true }; 
      sutDbContext.Setup(m => m.Album).Returns(mockAlbumSet.Object); 

      // Check if Album.Remove works inside this test 
      var albumToBeDeleted = new Album() { AlbumID = 1, Title = "TestAlbumName" }; 

      sutDbContext.Object.Album.Add(albumToBeDeleted); 
      Assert.Equal(1, (from a in sutDbContext.Object.Album select a).Count()); 

      sutDbContext.Object.Album.Remove(albumToBeDeleted); 
      Assert.Equal(0, (from a in sutDbContext.Object.Album select a).Count()); 

      // Actual Test 
      sutDbContext.Object.Album.Add(albumToBeDeleted); 
      sutDbContext.Setup(m => m.Album.Find(It.IsAny<int>())) 
      .Returns(albumToBeDeleted); 

      AlbumController sut = new AlbumController(sutDbContext.Object); 

      var output = sut.DeleteConfirmed(1); // Throws NotImplementedException 

      // Assert 
      Assert.Equal(0, (from a in sutDbContext.Object.Album select a).Count()); 
     } 
    } 

Le test lève l'exception suivante sur db.Album.Remove (album) dans DeleteConfirmed:

System.NotImplementedException: Le membre 'Supprimer' n'a pas été mis en œuvre sur le type 'DbSet 1Proxy' which inherits from 'DbSet 1'. Le test double pour 'DbSet`1' doit fournir des implémentations de méthodes et propriétés utilisées.

Comme vous pouvez le voir dans le corps de la méthode MockDbSet, je configuration Remove méthode pour ma maquette et il fonctionne très bien à l'intérieur du test unitaire. Pouvez-vous m'expliquer pourquoi cela ne fonctionne pas dans le contrôleur?

+0

Vous semblez injecter une implémentation de DbContext à votre contrôleur, pouvez-vous ajouter du code qui montre lequel? –

+0

@raderick J'injecte ApplicationDbContext mock (vous pouvez vérifier son code source en haut). L'initialisation est dans le test: AlbumController (sutDbContext.Object). C'est le même DbContext que j'ai utilisé dans le corps du test. – Outshined

+1

Le code de test semble globalement étrange, en lignes après '// Vérifier si Album.Remove fonctionne à l'intérieur de ce test' vous testez vos configurations fictives, ce qui est incorrect. Votre 'sut.DeleteConfirmed' teste également partiellement vos configurations Mock. Je vous conseille de passer votre DbSet à IDbSet et d'utiliser 'dbSetMock.Verify (x => x.Remove (albumToBeDeleted)) 'pour tester que le code est appelé sans tester les détails d'implémentation –

Répondre

1

Votre test fonctionnera bien si vous changez votre ligne:

sutDbContext.Setup(m => m.Album.Find(It.IsAny<int>())) 
      .Returns(albumToBeDeleted); 

Pour:

mockAlbumSet.Setup(x=>x.Find(It.IsAny<int>())) 
      .Returns(albumToBeDeleted); 

Vous avez une configuration pour votre sutDbContext revenir mockAlbumSet.Object quand sutDbContext.Album est appelé, mais cette ligne OPPOSER votre configuration pour créer un nouvel objet maquette pour la propriété sutDbContext.Album et a créé une configuration unique pour cette maquette:

m.Album.Find(It.IsAny<int>())) 
      .Returns(albumToBeDeleted); 

Voici un test simple, qui vous montre que l'appel configuration de la propriété imbriquée de la classe, qui a été précédemment installé pour retourner un Mock.Object, remplacera cette propriété avec une nouvelle Mock.Object:

public interface IParentService 
{ 
    IDependantService Dependant { get; } 
} 

public interface IDependantService 
{ 
    void Execute(); 
} 

[Fact] 
//This test passes 
public void VerifyThatNestedMockSetupGeneratesNewMockObject() 
{ 
    var value = 0; 

    var parentServiceMock = new Mock<IParentService>(); 
    var dependantServiceMock = new Mock<IDependantService>(); 
    dependantServiceMock.Setup(x => x.Execute()).Callback(() => { value = 1; }); 

    parentServiceMock.Setup(x => x.Dependant).Returns(dependantServiceMock.Object); 

    Assert.Same(parentServiceMock.Object.Dependant, dependantServiceMock.Object); 
    parentServiceMock.Setup(x => x.Dependant.Execute()).Callback(() => { value = 2; }); 
    Assert.NotSame(parentServiceMock.Object.Dependant, dependantServiceMock.Object); 

    parentServiceMock.Object.Dependant.Execute(); 

    Assert.Equal(2, value); 
}