2017-10-03 3 views
-2

Zuerst wird der Code,Unit Test Mock eine Klasse mehrere Schnittstellen und eine Klasse

Generic Interface vererben:

public interface IEntityService<TEntity> where TEntity : class 
{ 
    IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, 
     Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, 
     string includeProperties = ""); 

    Task<TEntity> GetByIDAsync(object id); 

    Task<TEntity> GetFirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate); 
} 

Allgemeine Klasse mit Interface-Implementierung:

public class EntityService<TEntity> : IEntityService<TEntity> where TEntity : class 
{ 
    protected IContext IContext; 
    protected DbSet<TEntity> IDbSet; 

    public EntityService(IContext context) 
    { 
     IContext = context; 
     IDbSet = IContext.Set<TEntity>(); 
    } 

    public virtual IQueryable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, 
     Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, 
     string includeProperties = "") 
    { 
     IQueryable<TEntity> query = IDbSet; 

     if (filter != null) 
     { 
      query = query.Where(filter); 
     } 

     query = includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProperty) => current.Include(includeProperty)); 

     if (orderBy != null) 
     { 
      return orderBy(query); 
     } 
     return query; 
    } 

    public virtual async Task<TEntity> GetByIDAsync(object id) 
    { 
     return await IDbSet.FindAsync(id); 
    } 

    public virtual async Task<TEntity> GetFirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate) 
    { 
     return await IDbSet.FirstOrDefaultAsync(predicate); 
    } 
} 

Spezifische Schnittstelle:

public interface ILoginService 
{ 
    Task<UserProfileViewModel> GetLoginDetailAsync(string userName); 
} 

Spezifische Klasse: Implementierung generische Klasse und spezifische Schnittstelle

public class LoginService : EntityService<UserAccount>, ILoginService 
{ 

    private readonly IContext _iContext; 

    public LoginService(IContext context): base(context) 
    { 
     _iContext = context; 
    } 

    async Task<UserProfileViewModel> ILoginService.GetLoginDetailAsync(string userName) 
    { 
     var userAcount = await GetFirstOrDefaultAsync(c => c.Username.ToLower() == userName.Trim().ToLower() && c.Active == true); 
     if (userAcount != null) 
     { 
      return Mapper.Map<UserAccount, UserProfileViewModel>(userAcount); 
     } 
     return null; 
    } 
} 

Nun soll ich LoginService die einzige Methode, um es

Hier ist der Testcode

hat testen
[Test] 
    public async Task GetLoginDetailAsync_InvalidUsername_ReturnsNull() 
    { 
     var userName = "should not exist!"; 
     var userAccount = new List<UserAccount>() 
     { 
      new UserAccount 
      { 
       ID = 1, 
       Name = "Test User" 
      } 
     }.AsQueryable(); 
     var mockSet = new Mock<DbSet<UserAccount>>(); 
     var userProfileViewModel = new UserProfileViewModel 
     { 
      ID = 1, 
      Name = Guid.NewGuid().ToString().Substring(0, 8) 
     }; 
     _context.Setup(c => c.Set<UserAccount>()).Returns(mockSet.Object); 
     loginService = new LoginService(_context.Object); 
     mockSet.As<IDbAsyncEnumerable<UserAccount>>(). 
      Setup(m => m.GetAsyncEnumerator()). 
      Returns(new TestDbAsyncEnumerator<UserAccount>(userAccount.GetEnumerator())); 
     mockSet.As<IQueryable<UserAccount>>() 
      .Setup(m => m.Provider) 
      .Returns(new TestDbAsyncQueryProvider<UserAccount>(userAccount.Provider)); 
     mockSet.As<IQueryable<UserAccount>>().Setup(m => m.Expression).Returns(userAccount.Expression); 
     mockSet.As<IQueryable<UserAccount>>().Setup(m => m.ElementType).Returns(userAccount.ElementType); 
     mockSet.As<IQueryable<UserAccount>>().Setup(m => m.GetEnumerator()).Returns(userAccount.GetEnumerator()); 

     var result = await ((ILoginService)loginService).GetLoginDetailAsync(userName); 
     Assert.IsNull(result); 
    } 

Jetzt diese TestDbAsyncEnumerator und TestDbAsyncQueryProvider werden von msdn genommen, um Async Abfragen in EF zu testen.

Das Problem

Der Test löst eine Ausnahme aus, dass Message: System.NotImplementedException : The member 'IQueryable.Provider' has not been implemented on type 'DbSet1Proxy' which inherits from 'DbSet1'. Test doubles for 'DbSet1' must provide implementations of methods and properties that are used. Grundsätzlich habe ich nicht Setup die FirstOrDefaultAsync für mockSet, die (es ruft EntityService in GetLoginDetailAsync genannt zu werden, dass die FirstOrDefaultAsync von IDbSet Aufruf endet).

Ich weiß nicht, wie kann ich das verspotten, weil die LoginService es nicht direkt erbt. Es erbt die EntityService, die wiederum diese generische Methode FirstOrDefaultAsync hat. Ich stehe fest, wie ich das einrichten soll.

Eine andere Sache, die ich dachte, war versuchen, diesen

var loginMock = new Mock<LoginService>(_context.Object); 
loginMock.As<ILoginService>().Setup(c => c.GetLoginDetailAsync(It.IsAny<string>())).Returns(Task.FromResult<UserProfileViewModel>(null)); 
loginMock.As<IEntityService<UserAccount>>().Setup(c => c.GetFirstOrDefaultAsync(It.IsAny<Expression<Func<UserAccount, bool>>>())).Returns(Task.FromResult(userAccount.First())); 

Aber ich glaube nicht, das ist der richtige Weg zu gehen, da ich nur das Mock-Objekt zu testen würde. Kann mir irgendjemand vorschlagen, wie ich zum Einrichten und Testen/Mock dies GetFirstOrDefaultAsync, oder bin ich völlig in eine falsche Richtung gehen?

UPDATE NACH ANTWORT:

Nach der Antwort von @ODawgG, ich bin Aktualisierung dieser. Der Test funktionierte gut, wie in der Antwort angegeben, aber jetzt ist der andere Test fehlgeschlagen. Ich wollte testen, ob ein bestimmter Benutzer das System verlässt.

Hier ist der Testcode: [Test] öffentliche async Aufgabe Test3() {

 var userAccount = new List<UserAccount>() 
     { 
      new UserAccount 
      { 
       ID = 1, 
       Username = "User" 
      } 
     }.AsQueryable(); 
     var mockSet = new Mock<DbSet<UserAccount>>(); 
     mockSet.As<IDbAsyncEnumerable<UserAccount>>(). 
      Setup(m => m.GetAsyncEnumerator()). 
      Returns(new TestDbAsyncEnumerator<UserAccount>(userAccount.GetEnumerator())); 
     mockSet.As<IQueryable<UserAccount>>() 
      .Setup(m => m.Provider) 
      .Returns(new TestDbAsyncQueryProvider<UserAccount>(userAccount.Provider)); 
     mockSet.As<IQueryable<UserAccount>>().Setup(m => m.Expression).Returns(userAccount.Expression); 
     mockSet.As<IQueryable<UserAccount>>().Setup(m => m.ElementType).Returns(userAccount.ElementType); 
     mockSet.As<IQueryable<UserAccount>>().Setup(m => m.GetEnumerator()).Returns(userAccount.GetEnumerator()); 

     AutoMapConfiguration.Configure(); 
     var entityService = new Mock<IEntityService<UserAccount>>(); 

     entityService 
      .Setup(service => service.GetFirstOrDefaultAsync(It.IsAny<Expression<Func<UserAccount, bool>>>())) 
      .ReturnsAsync(
       (Expression<Func<UserAccount, bool>> predicate) => userAccount.FirstOrDefault(predicate) 
      ); 

     var loginService = new LoginService(entityService.Object); 

     // Act 
     var result = await ((ILoginService)loginService).GetLoginDetailAsync("User"); 

     // Assert 
     Assert.IsNotNull(result); 
    } 

Dieser Test soll passieren, wie es auf dem userAccount Abfrage sollte aber es funktioniert nicht, wenn ich das Debuggen war, und es ging innerhalb der LoginService, und Ich überprüfte _entityService.Get().ToList() es sagt 0 zählen, während es wirklich zählen sollte zählen 1, , dass ich eingerichtet habe. Afaik, die IDbSet ist immer noch nicht eingerichtet, und deshalb ist die Anzahl 0, und es gibt nicht wahr zurück. Wie richte ich das ein? Wenn es richtig ist, warum ist dieser Test fehlgeschlagen? Außerdem weiß ich, dass moq ist nicht wirklich gut für den Ausdruck Test, aber ich habe diese predicate Teil des Codes von here.

+2

Haben Sie versucht, mit 'IDbSet' anstelle von' DbSet' zu spotten. In EF 5 wurde "IDbSet" eingeführt. Sie können dann einen Wert für die Eigenschaft "Provider" angeben. – Hintham

+2

Scheint seltsam, dass 'LoginService' von der tatsächlichen Implementierung von 'EntityService ' erbt? Sie haben den Anmeldeservice so eingerichtet, dass er eng mit der EF-Implementierung verbunden ist. Aus der Sicht des Tests sollte 'LoginService' beispielsweise eine Abstraktion von 'IEntityService ' als Parameter im Konstruktor nehmen. Dann können Sie Tests schreiben, bei denen das erwartete Prädikat an die '_entityService.GetFirstOrDefaultAsync'-Methode übergeben wurde. – Fabio

+0

@Downvoter: Ich habe den ganzen Code, das Problem, das ich hatte, und die Schritte, die ich unternommen habe, um mein Problem zu beheben, und wo sie fehlgeschlagen sind, erklärt. Ich weiß nicht, wie ich meine Frage weiter verbessern kann. Wenn Sie denken, dass immer noch etwas nicht in Ordnung ist, warum Sie abgelehnt haben, teilen Sie uns bitte zumindest Ihre Meinung mit, warum Ihnen die Frage nicht gefallen hat, was fehlte oder wie Sie die Frage weiter verbessern können. Ich denke nicht, dass das Ablehnen ohne Angabe eines Grundes für mich oder irgendjemanden, der diese Frage beantwortet oder liest, produktiv sein wird, danke! – Razort4x

Antwort

2

Ich stimme @Fabio zu. Es gibt keine Notwendigkeit, von EntityService<T> zu erben, sondern injizieren Sie in Ihre LogService Klasse.

Überarbeitete Ihre Klasse wie folgt aussehen würde:

[Test] 
public async Task GetLoginDetailAsync_InvalidUsername_ReturnsNull() 
{ 
    // Arrange 
    MapperInitialize.Configure(); 
    var entityService = new Mock<IEntityService<UserAccount>>(); 

    entityService 
     .Setup(service => service.GetFirstOrDefaultAsync(It.IsAny<Expression<Func<UserAccount, bool>>>())) 
     .ReturnsAsync(new UserAccount 
     { 
      ID = 1, 
      Name = "Test User" 
     }); 

    var loginService = new LoginService(entityService.Object); 

    // Act 
    var result = await ((ILoginService)loginService).GetLoginDetailAsync(It.IsAny<string>()); 

    // Assert 
    Assert.IsNotNull(result); 
} 

Hier ist die aktualisierte Test umfassen die Expression zu testen:

[Test] 
    public async Task GetLoginDetailAsync_InvalidUsername_ReturnsNull() 
    { 
     // Arrange 
     MapperInitialize.Configure(); 
     var entityService = new Mock<IEntityService<UserAccount>>(); 

     var userAccount = new UserAccount 
     { 
      ID = 1, 
      Username = "Test User", 
      Active = true 
     }; 

     var expressionResult = false; 
     entityService 
      .Setup(service => service.GetFirstOrDefaultAsync(It.IsAny<Expression<Func<UserAccount, bool>>>())) 
      .Callback<Expression<Func<UserAccount, bool>>>(expression => 
      { 
       expressionResult = expression.Compile().Invoke(userAccount); 
      }) 
      .ReturnsAsync(userAccount); 

     var loginService = new LoginService(entityService.Object); 

     // Act 
     var result = await ((ILoginService)loginService).GetLoginDetailAsync("Test User"); 

     // Assert 
     Assert.IsTrue(expressionResult); 
     Assert.IsNotNull(result); 
    } 

public class LoginService : ILoginService 
{ 
    private readonly IEntityService<UserAccount> _entityService; 

    public LoginService(IEntityService<UserAccount> entityService) 
    { 
     _entityService = entityService; 
    } 

    async Task<UserProfileViewModel> ILoginService.GetLoginDetailAsync(string userName) 
    { 
     var userAcount = await _entityService.GetFirstOrDefaultAsync(c => c.Username.ToLower() == userName.Trim().ToLower() && c.Active); 
     if (userAcount != null) 
     { 
      return Mapper.Map<UserAccount, UserProfileViewModel>(userAcount); 
     } 
     return null; 
    } 
} 

Und Ihre Test wie folgt aussehen würde,

+0

Ist Konstruktorinjektion ein guter Weg? Ich meine, ich könnte auch von "IEntityService", so etwas wie "öffentliche Klasse LoginService: ILoginService, IEntityService " erben, aber in diesem Fall müsste ich alle diese generischen Methoden in dieser Klasse implementieren, die redundant sein könnten. – Razort4x

+0

Ja, es ist definitiv eine gute Übung, um durch den Konstruktor zu injizieren. In SOLID-Prinzipien steht das D für "Dependency Inversion Principle". So hat LoginService keine direkte Abhängigkeit mehr von IEntityService, sondern hängt von einer Abstraktion ab. Dies ist der Schlüssel zu Ihrem Design bei der Verwaltung von Abhängigkeiten. Es hilft erheblich beim Testen der richtigen Einheit und bei der Verwendung von Frameworks und IOC-Containern. –

+0

Ich habe einige weitere Details hinzugefügt, bitte können Sie diesen Teil überprüfen? – Razort4x