Reputation: 607
I am new to unit testing. Not sure what to do my testing on, I went ahead and started one with an Index view on ClientController. I am having issues writing test method for the Index view which is declared as async Task. What i want to do is make sure this action returns data. Not sure how important this test is but I wanted to learn ways to write tests in MVC 5. Seems to have trouble writing tests for the view that are async.
ManagementService class
public IQueryable<Client> GetAllClients(bool includeDeleted = false)
{
var query = context.Clients.AsQueryable();
if (!includeDeleted) query = query.Where(c => !c.IsDeleted);
return query;
}
Client Controller
public ClientController(IApplicationContext appContext, IManagementService adminService, IDomainService domainService)
{
this.adminService = adminService;
this.appContext = appContext;
this.domainService = domainService;
}
public async Task<ActionResult> Index()
{
var clients = adminService.GetAllClients();
return View(await clients.ToListAsync());
}
//TEST
[TestMethod]
public void Index()
{
//Arrange
var mgmtmockContext = new Mock<IManagementService>();
List<Client> db = new List<Client>();
db.Add(new Client { ClientId = 1, Name = "Test Client 1" });
db.Add(new Client { ClientId = 2, Name = "Test Client 2" });
mgmtmockContext
.Setup(m => m.GetAllClients(false))
.Returns(() => { return db.AsQueryable(); });
//Act
ClientController controller = new ClientController(null, mgmtmockContext.Object, null);
var result = controller.Index() as Task<ActionResult>;
//Assert
Assert.IsNotNull(result);
}
After I create an instance of the controller, I am not sure how to pass the mock object to Index() so I can test this view returns data.
What I want to Assert is Assert.AreEqual(2, result.Count()) so I can run this test.
Upvotes: 9
Views: 15654
Reputation: 28737
In order to get the list out of the result you need to do two things:
Here's how you do this:
var result = controller.Index() as Task<ViewResult>;
// Get the actual result from the task
var viewresult = result.Result;
// Get the model from the viewresult and cast it to the correct type
// (notice in the first line I changed ActionResult to ViewResult to make sure we can access the model.
var model = (List<Client>)(viewresult.Model);
//Assert
Assert.IsNotNull(result);
Assert.AreEqual(2, model.Count);
Upvotes: 7
Reputation: 607
Figured this out. Testing async wasn't easy. Got the MockingHelper class from asp.net.
public static class MockingHelper
{
public static Mock<DbSet<TEntity>> AsMockDbSet<TEntity>(this IQueryable<TEntity> data)
where TEntity : class
{
var mockSet = new Mock<DbSet<TEntity>>();
mockSet.As<IQueryable<TEntity>>().Setup(m => m.Provider).Returns(new TestDbAsyncQueryProvider<TEntity>(data.Provider));
mockSet.As<IQueryable<TEntity>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<TEntity>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
mockSet.As<IDbAsyncEnumerable<TEntity>>().Setup(m => m.GetAsyncEnumerator()).Returns(new TestDbAsyncEnumerator<TEntity>(data.GetEnumerator()));
return mockSet;
}
public static Mock<DbSet<TEntity>> AsMockDbSet<TEntity>(this IEnumerable<TEntity> data)
where TEntity : class
{
return data.AsQueryable().AsMockDbSet();
}
}
And AsyncTestingClasses too.
internal class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
private readonly IQueryProvider _inner;
internal TestDbAsyncQueryProvider(IQueryProvider inner)
{
_inner = inner;
}
public IQueryable CreateQuery(Expression expression)
{
return new TestDbAsyncEnumerable<TEntity>(expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new TestDbAsyncEnumerable<TElement>(expression);
}
public object Execute(Expression expression)
{
return _inner.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
}
public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(expression));
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
internal class TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
public TestDbAsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{ }
public TestDbAsyncEnumerable(Expression expression)
: base(expression)
{ }
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
IQueryProvider IQueryable.Provider
{
get { return new TestDbAsyncQueryProvider<T>(this); }
}
}
internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner;
public TestDbAsyncEnumerator(IEnumerator<T> inner)
{
_inner = inner;
}
public void Dispose()
{
_inner.Dispose();
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_inner.MoveNext());
}
public T Current
{
get { return _inner.Current; }
}
object IDbAsyncEnumerator.Current
{
get { return Current; }
}
}
Then changed my TestMethod as below.
public void Default_Index_Returns_Data()
{
//Arrange
var appmockContext = new Mock<IApplicationContext>();
var mgmtmockContext = new Mock<IManagementService>();
var domainmockContext = new Mock<IDomainService>();
var agency1 = new Agency() { Name = "Test Agency" };
List<Client> db = new List<Client>();
db.Add(new Client { ClientId = 1, Name = "Test Client 1", AgencyId = 1 });
db.Add(new Client { ClientId = 2, Name = "Test Client 2", AgencyId = 1 });
//Act
mgmtmockContext
.Setup(m => m.GetAllClients(It.IsAny<bool>()))
.Returns(() => { return db.AsMockDbSet().Object; });
ClientController controller = new ClientController(appmockContext.Object, mgmtmockContext.Object, domainmockContext.Object);
var resultTask = controller.Index();
resultTask.Wait();
var result = resultTask.Result;
var model = (List<Client>)((ViewResult)result).Model;
//Assert
Assert.IsNotNull(result);
Assert.AreEqual(2, model.Count());
}
This worked. Thanks Alexei and Kenneth for your help. I hope this helps everyone.
Upvotes: 4
Reputation: 100545
You can either get controller.Index().Result
or change test to be async
:
[TestMethod]
public async Task Index()
{
....
var result = await controller.Index();
Assert.IsNotNull(result);
var model = (List<Client>)((ViewResult)result).Model;
Assert.AreEqual(2, model.Count())
}
Upvotes: 5