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