I have a method that should return all projects with a modified date older than 5 years.
var projekte = await this.db.Projects.Include(x => x.ProjectStatus)
.Where(x =>
x.ModifiedDate.HasValue
&& EF.Functions.DateDiffYear(
x.ModifiedDate.Value.AddYears(-5),
this.dateTimeProvider.Today) >= 5)
.ToListAsync(cancellationToken: cancellationToken);
and my unit test to cover this method:
[TestMethod]
public async Task Handle_WhenCalled_ThenReturnProjekteWithGLKenntnisnameOlderThan5Years()
{
var today = new DateTime(2022, 8, 1);
var projekt1 = new ProjektBuilder()
.WithProjektId(new Guid("5a38062d-1992-4110-a49f-04cdf1eb21f0"))
.WithModifiedDate(today.AddYears(-5))
.Build();
var projekt2 = new ProjektBuilder()
.WithProjektId(new Guid("b5deaec7-17dd-4f2e-83fe-1badd7deeadb"))
.WithModifiedDate(today.AddYears(-6))
.Build();
var projekt3 = new ProjektBuilder()
.WithProjektId(new Guid("a7ca47ec-e5b0-4268-8df1-da562af1acd7"))
.WithModifiedDate(today.AddYears(-5).AddDays(1))
.Build();
this.AddToInMemoryContext(new[] { projekt1, projekt2, projekt3 });
this.dateTimeProviderFake.Setup(x => x.Today).Returns(today);
var result = await this.testee.Handle(new GetProjekteToArchiveQuery(), CancellationToken.None);
result.Should().BeEquivalentTo(new[] { projekt1, projekt2 });
}
The InMemoryDb is setup like this:
protected void InitializeInMemoryContext()
{
var randomAuditDatabaseName = $"{nameof(IAuditingContext)}_{Guid.NewGuid()}";
var auditOptions = new DbContextOptionsBuilder<AuditDbContext>().UseInMemoryDatabase(randomAuditDatabaseName).Options;
var auditContext = new AuditDbContext(auditOptions);
var randomDatabaseName = $"{nameof(TContextInterface)}_{Guid.NewGuid()}";
var options = new DbContextOptionsBuilder<TContext>().UseInMemoryDatabase(randomDatabaseName, b => b.EnableNullChecks(false)).EnableSensitiveDataLogging().Options;
this.InMemoryContext = (TContext)Activator.CreateInstance(typeof(TContext), options);
}
protected int AddToInMemoryContext<TEntity>(ICollection<TEntity> entities)
{
foreach (var entity in entities)
{
this.InMemoryContext.Add(entity);
}
return this.InMemoryContext.SaveChangesAsync().Result;
}
When running the unit test I get the following error:
System.InvalidOperationException: The 'DateDiffYear' method is not supported because the query has switched to client-evaluation. This usually happens when the arguments to the method cannot be translated to server. Rewrite the query to avoid client evaluation of arguments so that method can be translated to server.
at Microsoft.EntityFrameworkCore.SqlServerDbFunctionsExtensions.DateDiffYear(DbFunctions _, DateTime startDate, DateTime endDate)
I guess it's because the DateDiffYear is specifically for the SQLServer but how can I unit test this method if this is the case?
Thanks in advance
For this case, the best approach for really testing your code is to have a docker image with an SQL Server instance. The downside of this approach is that your unit tests would have a dependency, so in your CI/CD pipelines you will also need an SQL Server instance.
When you do not depend on any specific DB function, you can use InMemory or SQLite in memory, otherwise you need to test against the real technology. This is also the recommended approach by Microsoft:
Testing EF Core Applications
Testing against your production database system
Related
[Fact]
public async Task Test_GetCustomer_Status_When_No_Customer_Exist()
{
var customerEngine = A.Fake<ICustomerEngine>();
var monitorEngine = A.Fake<IMonitorEngine>();
var customerLog = A.Fake<ILogProvider<CustomerManager>>();
var conditions = new List<ScanCondition>();
var customers = new List<Customer>();
var names = new List<string>
{
"SERVICE"
};
var hashKey = "SITE";
A.CallTo(() => customerEngine.GetCustomers(conditions)).Returns(null); // Get customers returns AsyncSearch<Customer>
A.CallTo(() => monitorEngine.QueryItems(hashKey, QueryOperator.BeginsWith, names)).Returns(null);
CustomerManager manager = new CustomerManager(customerEngine, monitorEngine);
await manager.GetCustomers();
A.CallTo(() => customerEngine.GetCustomers(A<List<ScanCondition>>.Ignored)).MustHaveHappened();
}
Get customers returns AsyncSearch
But, How to mock AsyncSearch?
Is there any work around for this now?
I do not see the interface for this class hence it would be tough to fake it. I see this issue reported to AWS, if they come back with an fix in future, we can do it. There is work around as you will end up in doing lot of R&D. Refer this https://github.com/aws/aws-sdk-net/issues/772 and
https://github.com/aws/aws-sdk-net/issues/772
This is possible now as of version 3.7.4.9 of the AWSSDK.DynamoDBv2 package, as Norm Johanson mentions on the Github issue. You can mock AsyncSearch by creating your own subclass as he shows, or by using Moq like so:
var mockAsyncSearch = new Mock<AsyncSearch<YourClass>>();
mockAsyncSearch
.Setup(a => a.GetRemainingAsync(...))
.Returns(...);
And then your mocked IDynamoDbContext can just use .Returns(mockAsyncSearch.Object) as usual.
I am currently developing an app store style API which has the following entities (plus many others, but not relevant to the problem):
App (1 to many relationship to AppRevision - contains IEnumerable property)
AppRevision
Installation
I have come across an odd problem where the behaviour of EF differs in unit tests to when actually running the API, in that navigation properties are automatically being included when unit testing.
Take the following code snippet from my command handler:
App app = await this.context.Apps
.Include(a => a.Installations)
.FirstOrDefaultAsync(a => a.Id == command.AppId);
if (app != null) {
// Code omitted for brevity
}
When running the API, if I inspect app after this code has been run, the AppRevisions collection on the App entity is empty, as you would expect as I have not expliclity told EF to .Include(a => a.AppRevisions) - the API then throws an exception when trying to process code later on that needs this data to be there.
Now look at the following unit test for the same handler:
[Fact]
public async void Handle_ShouldAddInstallationRecord_WhenDataIsValid()
{
Guid testGuid = Guid.NewGuid();
CreateInstallationCommand command = new CreateInstallationCommand(testGuid, "ABC", "abc#abc.com", null);
using (TestContext context = new TestContextFactory().CreateTestContext())
{
context.Apps.Add(new App() { Id = testGuid });
context.AppRevisions.Add(new AppRevision() { Id = Guid.NewGuid(), AppId = testGuid, Status = AppRevisionStatus.Approved, IsListed = true });
await context.SaveChangesAsync();
CreateInstallationCommandHandler handler = new CreateInstallationCommandHandler(context);
CommandResult result = await handler.Handle(command, new CancellationToken());
Assert.True(result);
Assert.Single(context.Installations);
}
}
If I step through this test, when I get to the handler and inspect the app variable, the AppRevisions collection has automatically been populated. As a result, the test passes because the code that requires the AppRevisions collection to be populated can execute.
The expectation is that this test should actually fail, because I'm not telling EF to include those entities in the query.
I am using a Sqlite in memory database to create the database context for my unit tests and running .NET Core 2.2
I originally thought this was something to do with the changetracker. While disabling this does solve the immediate problem reported above, it creates a load of other problems so isn't a viable solution (and probably wouldn't be the correct one anyway)
Any suggestions gratefully received
For anyone who comes across this post in the future, the solution is as per the comments on the original question, to use separate contexts for seeding test data and getting the data later in the test:
[Fact]
public async void Handle_ShouldAddInstallationRecord_WhenDataIsValid()
{
Guid testGuid = Guid.NewGuid();
CreateInstallationCommand command = new CreateInstallationCommand(testGuid, "ABC", "abc#abc.com", null);
using (TestContextFactory contextFactory = new TestContextFactory())
{
using (TestContext seedContext = contextFactory.CreateTestContext())
{
seedContext.Apps.Add(new App() { Id = testGuid });
seedContext.AppRevisions.Add(new AppRevision() { Id = Guid.NewGuid(), AppId = testGuid, Status = AppRevisionStatus.Approved, IsListed = true });
await seedContext.SaveChangesAsync();
}
using (TestContext getContext = contextFactory.CreateTestContext())
{
CreateInstallationCommandHandler handler = new CreateInstallationCommandHandler(getContext);
CommandResult result = await handler.Handle(command, new CancellationToken());
Assert.True(result);
Assert.Single(getContext.Installations);
}
}
}
Sorry, this is likely a very amateur question, but I am struggling to understand how to use Moq properly. I am quite new to unit testing as a whole, but I think I'm starting to get the hang of it.
So here's my question... I have this snippet of code below which is using a TestServer in Visual Studio that I using for am Unit Testing... I'm trying to mock IGamesByPublisher so that my test is not reliant on data in the repository (or would it be better to mock GamesByPublisher?... Or do I need to do both?)
public static TestServerWithRepositoryService => new TestServer(services =>
{
services.AddScoped<IGamesByPublisher, GamesByPublisher();
}).AddAuthorization("fake.account", null);
[Fact] // 200 - Response, Happy Path
public async Task GamesByPublisher_GamesByPublisherLookup_ValidRequestData_Produces200()
{
// Arrange
var server = ServerWithRepositoryService;
// Act
var response = await server.GetAsync(Uri);
// Assert
Assert.NotNull(response);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
Here is the IGamesByPublisher
public interface IGamesByPublisher interface.
{
Task<IEnumerable<Publisher>> Execute(GamesByPublisherQueryOptions options);
}
}
I tried
public static TestServerWithRepositoryService => new TestServer(services =>
{
services.AddScoped<Mock<IGamesByPublisher>, Mock<GamesByPublisher>>();
}).AddAuthorization("fake.account", null);
And then I tried
// Not exactly what I attempted, but that code is long gone...
var mock = new Mock<IGamesByPublisher >();
var foo = new GamesByPublisherQueryOptions();
mock.Setup(x => x.Execute(foo)).Returns(true);
I didn't really find great documentation on using Moq, just the quick start guide on GitHub, which I wasn't sure how to apply (probably my own level of experience at fault there...).
I am obviously missing some fundamentals on using Moq...
You were close.
public static TestServerWithRepositoryService => new TestServer(services => {
var mock = new Mock<IGamesByPublisher>();
var publishers = new List<Publisher>() {
//...populate as needed
};
mock
.Setup(_ => _.Execute(It.IsAny<GamesByPublisherQueryOptions>()))
.ReturnsAsync(() => publishers);
services.RemoveAll<IGamesByPublisher>();
services.AddScoped<IGamesByPublisher>(sp => mock.Object);
}).AddAuthorization("fake.account", null);
The above creates the mock, sets up its expected behavior to return a list of publishers any time Execute is invoked with a GamesByPublisherQueryOptions.
It then removes any registrations of the desired interface to avoid conflicts and then registers the service to return the mock any time the interface is requested to be resolved.
There seems to be little information about how to write good unit tests for actual ASP.NET Core controller actions. Any guidance about how to make this work for real?
I've got a system that seems to be working pretty well right now, so I thought I'd share it and see if it doesn't help someone else out. There's a really useful article in the Entity Framework documentation that points the way. But here's how I incorporated it into an actual working application.
1. Create an ASP.NET Core Web App in your solution
There are tons of great articles out there to help you get started. The documentation for basic setup and scaffolding is very helpful. For this purpose, you'll want to create a web app with Individual User Accounts so that your ApplicationDbContext is setup to work with EntityFramework automatically.
1a. Scaffold a controller
Use the information included in the documentation to create a simple controller with basic CRUD actions.
2. Create a separate class library for your unit tests
In your solution, create a new .NET Core Library and reference your newly created web app. In my example, the model I'm using is called Company, and it uses the CompaniesController.
2a. Add the necessary packages to your test library
For this project, I use xUnit as my test runner, Moq for mocking objects, and FluentAssertions to make more meaningful assertions. Add those three libraries to your project using NuGet Package Manager and/or Console. You may need to search for them with the Show Prerelease checkbox selected.
You will also need a couple of packages to use EntityFramework's new Sqlite-InMemory database option. This is the secret sauce. Below are a list of the package names on NuGet:
Microsoft.Data.Sqlite
Microsoft.EntityFrameworkCore.InMemory [emphasis added]
Microsoft.EntityFrameworkCore.Sqlite [emphasis added]
3. Setup Your Test Fixture
Per the article I mentioned earlier, there is a simple, beautiful way to set up Sqlite to work as an in-memory, relational database which you can run your tests against.
You'll want to write your unit test methods so that each method has a new, clean copy of the database. The article above shows you how to do that on a one-off basis. Here's how I set up my fixture to be as DRY as possible.
3a. Synchronous Controller Actions
I've written the following method that allows me to write tests using the Arrange/Act/Assert model, with each stage acting as a parameter in my test. Below is the code for the method and the relevant class properties in the TestFixture that it references, and finally an example of what it looks like to call the code.
public class TestFixture {
public SqliteConnection ConnectionFactory() => new SqliteConnection("DataSource=:memory:");
public DbContextOptions<ApplicationDbContext> DbOptionsFactory(SqliteConnection connection) =>
new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlite(connection)
.Options;
public Company CompanyFactory() => new Company {Name = Guid.NewGuid().ToString()};
public void RunWithDatabase(
Action<ApplicationDbContext> arrange,
Func<ApplicationDbContext, IActionResult> act,
Action<IActionResult> assert)
{
var connection = ConnectionFactory();
connection.Open();
try
{
var options = DbOptionsFactory(connection);
using (var context = new ApplicationDbContext(options))
{
context.Database.EnsureCreated();
// Arrange
arrange?.Invoke(context);
}
using (var context = new ApplicationDbContext(options))
{
// Act (and pass result into assert)
var result = act.Invoke(context);
// Assert
assert.Invoke(result);
}
}
finally
{
connection.Close();
}
}
...
}
Here's what it looks like to call the code to test the Create method on the CompaniesController (I use parameter names to help me keep my expressions straight, but you don't strictly need them):
[Fact]
public void Get_ReturnsAViewResult()
{
_fixture.RunWithDatabase(
arrange: null,
act: context => new CompaniesController(context, _logger).Create(),
assert: result => result.Should().BeOfType<ViewResult>()
);
}
My CompaniesController class requires a logger, that I mock up with Moq and store as a variable in my TestFixture.
3b. Asynchronous Controller Actions
Of course, many of the built-in ASP.NET Core actions are asynchronous. To use this structure with those, I've written the method below:
public class TestFixture {
...
public async Task RunWithDatabaseAsync(
Func<ApplicationDbContext, Task> arrange,
Func<ApplicationDbContext, Task<IActionResult>> act,
Action<IActionResult> assert)
{
var connection = ConnectionFactory();
await connection.OpenAsync();
try
{
var options = DbOptionsFactory(connection);
using (var context = new ApplicationDbContext(options))
{
await context.Database.EnsureCreatedAsync();
if (arrange != null) await arrange.Invoke(context);
}
using (var context = new ApplicationDbContext(options))
{
var result = await act.Invoke(context);
assert.Invoke(result);
}
}
finally
{
connection.Close();
}
}
}
It's almost exactly the same, just setup with async methods and awaiters. Below, an example of calling these methods:
[Fact]
public async Task Post_WhenViewModelDoesNotMatchId_ReturnsNotFound()
{
await _fixture.RunWithDatabaseAsync(
arrange: async context =>
{
context.Company.Add(CompanyFactory());
await context.SaveChangesAsync();
},
act: async context => await new CompaniesController(context, _logger).Edit(1, CompanyFactory()),
assert: result => result.Should().BeOfType<NotFoundResult>()
);
}
3c. Async Actions with Data
Of course, sometimes you'll have to pass data back-and-forth between the stages of testing. Here's a method I wrote that allows you to do that:
public class TestFixture {
...
public async Task RunWithDatabaseAsync(
Func<ApplicationDbContext, Task<dynamic>> arrange,
Func<ApplicationDbContext, dynamic, Task<IActionResult>> act,
Action<IActionResult, dynamic> assert)
{
var connection = ConnectionFactory();
await connection.OpenAsync();
try
{
object data;
var options = DbOptionsFactory(connection);
using (var context = new ApplicationDbContext(options))
{
await context.Database.EnsureCreatedAsync();
data = arrange != null
? await arrange?.Invoke(context)
: null;
}
using (var context = new ApplicationDbContext(options))
{
var result = await act.Invoke(context, data);
assert.Invoke(result, data);
}
}
finally
{
connection.Close();
}
}
}
And, of course, an example of how I use this code:
[Fact]
public async Task Post_WithInvalidModel_ReturnsModelErrors()
{
await _fixture.RunWithDatabaseAsync(
arrange: async context =>
{
var data = new
{
Key = "Name",
Message = "Name cannot be null",
Company = CompanyFactory()
};
context.Company.Add(data.Company);
await context.SaveChangesAsync();
return data;
},
act: async (context, data) =>
{
var ctrl = new CompaniesController(context, _logger);
ctrl.ModelState.AddModelError(data.Key, data.Message);
return await ctrl.Edit(1, data.Company);
},
assert: (result, data) => result.As<ViewResult>()
.ViewData.ModelState.Keys.Should().Contain((string) data.Key)
);
}
Conclusion
I really hope this helps somebody getting on their feet with C# and the awesome new stuff in ASP.NET Core. If you have any questions, criticisms, or suggestions, please let me know! I'm still new at this, too, so any constructive feedback is invaluable to me!
I'm a newbie in integration tests and I'm looking for some explanation and advice about workaround of my issue:
I'm using TransactionScope in my tests to keep database clean and creating new TransactionScope before each test and dispose it after each test:
[SetUp]
public void Init()
{
this.scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions() { IsolationLevel = IsolationLevel.ReadUncommitted },
TransactionScopeAsyncFlowOption.Enabled);
this.context = new SportsPerformanceDbContext();
this.questRepo = new QuestionRepository(this.context);
}
[TearDown]
public void CleanAll()
{
this.context.Dispose();
this.scope.Dispose();
}
Everything works ok when I run one test class. But when I run at least two test classes, I'm facing a problem: in this test (see below) lasrQuestionId equals the last question id from database - that's ok, but actualResultId equals the Id_of_the_last_added_question_in_tests_with_transaction_scope + 1:
[Test]
public async void AddAsyncTest()
{
// Arrange
var questionModel = new QuestionModel
{
//some properties
};
Question lastQuestion = this.GetLastQuestion();
var lastQuestionId = lastQuestion?.Id ?? 0;
// Act
var addResult = await this.questRepo.AddAsync(questionModel);
var actualResult = addResult.Value;
// Assert
Assert.AreEqual(lastQuestionId + 1, actualResult.Id);
// some other assertions
}
So I have the following, e.g., lastQuestionId is 5 (5 questions in Database), but actualResult Id is 16 (because I've previously added some questions in other tests)... I assume that there is a problem with my context or scope.dispose(). I don't know where is a problem, could you explain what am I doing wrong here? Thanks in advance!
this.GetLastQuestion() code is below:
private Question GetLastQuestion()
{
using (var ctx = new SportsPerformanceDbContext())
{
return ctx.Question
.OrderByDescending(q => q.Id)
.FirstOrDefault();
}
}
This is how SQL Server engine works:
For a given identity property with specific seed/increment, the identity values are not reused by the engine. If a particular insert statement fails or if the insert statement is rolled back then the consumed identity values are lost and will not be generated again. This can result in gaps when the subsequent identity values are generated.