Whenever we like to
develop using test-driven development approach, Unit Testing is always
essential. When it comes to .Net Core and .Net Entity Framework it little bit
different, no matter what unit testing framework we use, we need to mock
libraries.
Assuming we have .Net
core solution with test project, all we need to add the Moq from NuGet.
Install-Package Moq
Issue:
One of the issue which I
encountered while testing, the asynchronous call to the database was throwing
the following error.
System.InvalidOperationException: The provider for the source
IQueryable doesn't implement IDbAsyncQueryProvider. Only providers that
implement IDbAsyncQueryProvider can be used for Entity Framework asynchronous
operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.
Here is my fucntino that I wanted to test:
public async Task<string> GetTrainingTypeDescription(string code)
{
var trainingType = await
_dbContext.TrainingType.Where(x =>
x.TrainingTypeCode.Equals(code)).FirstOrDefaultAsync();
if (trainingType != null)
return
trainingType.Descriptions.ToString();
return string.Empty;
}
|
I wanted to test above function with in-memory set of data, the test failed because our test data
won’t support the interfaces needed to make the asynchronous call. The reason
behind this failure because the traditional provider for IQuerable and IEnumerable
doesn't implement the IAsyncQueryProvider
interface needed for the entity framework asynchronous extension method. We
need to create the supporting classes that will help us to mock the
asynchronous calls.
internal class TestAsyncEnumerable<T> :
EnumerableQuery
{
public TestAsyncEnumerable(IEnumerable
: base(enumerable)
{ }
public TestAsyncEnumerable(Expression expression)
: base(expression)
{ }
public IAsyncEnumerator
{
return GetEnumerator();
}
public IAsyncEnumerator
{
return new
TestAsyncEnumerator
}
IQueryProvider
IQueryable.Provider
{
get { return new
TestAsyncQueryProvider
}
}
internal class TestAsyncEnumerator<T> :
IAsyncEnumerator
{
private readonly IEnumerator
public TestAsyncEnumerator(IEnumerator
{
_inner =
inner;
}
public ValueTask DisposeAsync()
{
_inner.Dispose();
return new ValueTask();
}
public T Current => _inner.Current;
public ValueTask<bool> MoveNextAsync()
{
return new ValueTask<bool>(_inner.MoveNext());
}
}
internal class TestAsyncQueryProvider<TEntity> : IAsyncQueryProvider
{
private readonly IQueryProvider _inner;
internal TestAsyncQueryProvider(IQueryProvider inner)
{
_inner =
inner;
}
public IQueryable CreateQuery(Expression
expression)
{
return new TestAsyncEnumerable
}
public IQueryable
{
return new TestAsyncEnumerable
}
public object Execute(Expression expression)
{
return _inner.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return
_inner.Execute
}
public IAsyncEnumerable
{
return new TestAsyncEnumerable
}
public TResult ExecuteAsync<TResult>(Expression expression,
CancellationToken cancellationToken)
{
return
_inner.Execute
}
}
|
Now that we have created
our supported classes. We need to make sure when we return mock version of the DbSet
containing the data we pass as the TestData parameter. The resulting
DbSet will have support to asynchronous calls because it will
implement our custom classes.
To do that we need to
create following helping class.
public class UnitTestHelper
{
public static Mock
{
var mockSet = new Mock
mockSet.As
.Setup(x
=> x.GetAsyncEnumerator(default))
.Returns(new TestAsyncEnumerator
mockSet.As
.Setup(x
=> x.Provider)
.Returns(new
TestAsyncQueryProvider
mockSet.As
.Setup(x
=> x.Expression)
.Returns(testData.Expression);
mockSet.As
.Setup(x
=> x.ElementType)
.Returns(testData.ElementType);
mockSet.As
.Setup(x
=> x.GetEnumerator())
.Returns(testData.GetEnumerator());
mockSet.As
.Setup(x
=> x.GetEnumerator())
.Returns(testData.GetEnumerator());
return mockSet;
}
}
|
Next Step is to create
the data:
private static IQueryable
{
return new List
{
new TrainingType()
{
TrainingTypeCode = "Qa",
Descriptions = " Quality Assurance
Training",
Name
= "QA
Training"
},
new TrainingType()
{
TrainingTypeCode = "Lab",
Descriptions = "Laboratory Training.",
Name
= "Labs
Training."
}
}.AsQueryable();
}
|
Now that we have the
test data and the helper classes needed for that data to be accessed
asynchronously by Entity Framework, Lets write our test function.
So given our function GetTrainingTypeDescription(), we want to test it to make sure it works and it’s returning
the correct value.
[Fact]
public async void
When_querying_for_a_TrainingTypeDescription__returns_Description()
{
var context = new Mock
var trainingtype = GetTrainingTypes();
var cancellationToken = new CancellationToken();
context.SetupGet(c
=> c.TrainingType)
.Returns(UnitTestHelper.GetQueryableMockDbSet
// Call the
function to test
var typeHandler = new TypeHandler(context.Object);
var result = await TypeHandler.GetTrainingTypeDescription("Lab");
Assert.True(result.HasValue);
}
|
Now, Run the Test. It
will be successfully completed without errors. You can debug and see how it
works.
No comments:
Post a Comment