Integration Testing for ASP.NET Core using EF Core Cosmos with XUnit and Azure DevOps (2024)

This article shows how integration tests could be implemented for an ASP.NET Core application which uses EF Core and Azure Cosmos. The database tests can be run locally or in an Azure DevOps build using the Azure Cosmos emulator. XUnit is used to implement the tests.

Code: https://github.com/damienbod/AspNetCoreEfCoreCosmosTesting

EF Core is used to the access Azure Cosmos database. An EF Core DbContext was created to access Cosmos. This is like any EF Core context, with the DBSet definitions as required. Some Cosmos specific definitions are added using the OnModelCreating method. See the Cosmos-specific model customization for more details.

 public class CosmosContext : DbContext { public CosmosContext(DbContextOptions<CosmosContext> options) : base(options) { } public DbSet<MyData> MyData { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasDefaultContainer("MyDataStore"); modelBuilder.Entity<MyData>() .ToContainer("MyDataItems"); modelBuilder.Entity<MyData>() .HasPartitionKey(o => o.PartitionKey); modelBuilder.Entity<MyData>() .Property(d => d.ETag) .IsETagConcurrency(); } }

The MyData class is is used to model the Cosmos documents. This has a PartitionKey and also an ETag which can be used for the Optimistic concurrency validation.

public class MyData{public string Id { get; set; }public string PartitionKey { get; set; }public string Name { get; set; }public string Description { get; set; }public string ETag { get; set; }}

The MyDataService service class is used to access the context and implement some query logic as required. I like to keep this simple and not separate the specification of the queries from the the business or the Linq statements. This reduces the amount of code and keeps the data access, business simple and makes it easy to adapt.

public class MyDataService{private CosmosContext _cosmosContext;public MyDataService(CosmosContext cosmosContext){_cosmosContext = cosmosContext;}public void EnsureCreated(){_cosmosContext.Database.EnsureCreated();}public async Task CreateAsync(MyData myData){await _cosmosContext.MyData.AddAsync(myData);await _cosmosContext.SaveChangesAsync(false);}public async Task<MyData> Get(string id){return await _cosmosContext.MyData.FirstAsync(d => d.Id == id);}public async Task<IList<MyData>> NameContains(string name){return await _cosmosContext.MyData.Where(d => d.Name.Contains(name)).ToListAsync();}}

The ConfigureServices method adds the services required to use EF Core and Cosmos DB. The services are used in a Razor page application, but this could be any web application, ASP.NET Core API or ASP.NET Core Blazor.

public void ConfigureServices(IServiceCollection services) { services.AddDbContext<CosmosContext>(options => { options.UseCosmos( "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", databaseName: "MyDataDb" ); }); services.AddScoped<MyDataService>(); services.AddRazorPages(); }

The service needs to be tested. Instead of mocking away the database or using separate specifications classes as parameters, the service can be tested as one using Azure Cosmos emulator and EF Core. We used the framework tools to test our code. An EF Core in-memory database could also be used instead of the Azure Cosmos emulator. We use the emulator for these tests.

The tests are setup to add the services to the IoC and build these. The code can be run and asserted as required. To start locally in dev, the Azure Cosmos emulator needs to be started first.

using AspNetCoreCosmos.DataAccess;using Microsoft.EntityFrameworkCore;using Microsoft.Extensions.DependencyInjection;using System;using System.Threading.Tasks;using Xunit;namespace AspNetCoreCosmos.DbTests{ public class MyDataTests : IAsyncLifetime { private ServiceProvider _serviceProvider; public ServiceProvider ServiceProvider { get; set; } [Fact] public async Task MyDataCreateAsync() { using (var scope = _serviceProvider.CreateScope()) { // Arrange var myData = new MyData { Id = Guid.NewGuid().ToString(), PartitionKey = "Test", Name = "testData", Description = "test description" }; var myDataService = scope.ServiceProvider.GetService<MyDataService>(); myDataService.EnsureCreated(); // Act await myDataService.CreateAsync(myData); var first = await myDataService.Get(myData.Id); // Arrange Assert.Equal(myData.Id, first.Id); } } public Task InitializeAsync() { var serviceCollection = new ServiceCollection(); serviceCollection.AddDbContext<CosmosContext>(options => { options.UseCosmos( "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", databaseName: "MyDataDb" ); }); serviceCollection.AddScoped<MyDataService>(); _serviceProvider = serviceCollection.BuildServiceProvider(); return Task.CompletedTask; } public Task DisposeAsync() { return Task.CompletedTask; } }}

The integration tests can be run in the Azure DevOps CI. I used a yaml file to test this and added this to my Azure DevOps build. This was a little bit tricky to setup because I did not easily find any working docs. The Microsoft.Azure.CosmosDB.Emulator is installed and started using Powershell. Then the tests can be run.

Note: Cosmos db emulator is pre-installed on the windows-latest vm hosted image on Azure DevOps . Docs here –https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md.


Thank you Lohith for researching this!

trigger:- mainvariables: solution: '**/*.sln' buildPlatform: 'Any CPU' buildConfiguration: 'Release' vmImage: 'windows-latest'stages:- stage: Build displayName: Build .NET sln pool: vmImage: $(vmImage) jobs: - job: Build displayName: Build pool: vmImage: $(vmImage) steps: - task: NuGetToolInstaller@1 - task: NuGetCommand@2 inputs: restoreSolution: '$(solution)' - task: VSBuild@1 inputs: solution: '$(solution)' msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"' platform: '$(buildPlatform)' configuration: '$(buildConfiguration)' - task: VSTest@2 inputs: platform: '$(buildPlatform)' configuration: '$(buildConfiguration)'- stage: IntegrationTests displayName: Integration Tests dependsOn: Build pool: vmImage: $(vmImage) jobs: - job: intgrationtests displayName: Run integration tests steps: - task: DotNetCoreCLI@2 displayName: Restore inputs: command: 'restore' - task: PowerShell@2 displayName: 'Starting Cosmos Emulator' inputs: targetType: 'inline' workingDirectory: $(Pipeline.Workspace) script: | Write-Host "Starting CosmosDB Emulator" Import-Module "C:/Program Files/Azure Cosmos DB Emulator/PSModules/Microsoft.Azure.CosmosDB.Emulator" Start-CosmosDbEmulator - task: DotNetCoreCLI@2 displayName: "Cosmos Database Tests" inputs: command: test projects: "**/*.DbTests.csproj"

You can add the yaml pipeline to your Azure DevOps build and it will run like the triggers are defined or the Azure DevOps policies.

Integration Testing for ASP.NET Core using EF Core Cosmos with XUnit and Azure DevOps (1)

This works good, but you have to be careful in preparing the tests and running in parallel. Implementing the tests like this means you have less code in your application and you can still fully test all your code. A disadvantage with this approach is that the tests take longer to run compared to unit tests without the emulator.

Links

https://docs.microsoft.com/en-us/ef/core/providers/cosmos/

https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests

https://docs.microsoft.com/en-us/azure/cosmos-db/

https://dev.azure.com/

https://xunit.net/

https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator

Tags: ASP.NET Core, Azure, cosmos, devops, EF Core, ef-core

Integration Testing for ASP.NET Core using EF Core Cosmos with XUnit and Azure DevOps (2024)

References

Top Articles
Latest Posts
Article information

Author: Moshe Kshlerin

Last Updated:

Views: 5942

Rating: 4.7 / 5 (77 voted)

Reviews: 92% of readers found this page helpful

Author information

Name: Moshe Kshlerin

Birthday: 1994-01-25

Address: Suite 609 315 Lupita Unions, Ronnieburgh, MI 62697

Phone: +2424755286529

Job: District Education Designer

Hobby: Yoga, Gunsmithing, Singing, 3D printing, Nordic skating, Soapmaking, Juggling

Introduction: My name is Moshe Kshlerin, I am a gleaming, attractive, outstanding, pleasant, delightful, outstanding, famous person who loves writing and wants to share my knowledge and understanding with you.