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

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:
- main

variables:
  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.

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

7 comments

  1. […] Integration Testing for ASP.NET Core using EF Core Cosmos with XUnit and Azure DevOps (Damien Bowden) […]

  2. […] Integration Testing for ASP.NET Core using EF Core Cosmos with XUnit and Azure DevOps – Damien Bowden […]

  3. Lohith · · Reply

    Excellent article as usual…

    What caught me off guard was “Who installed the Cosmos DB Emulator on the vm image” :). Coz your YAML doesnt have a install cosmos db emulator but just a PS script to start it.
    I backtracked to look at the softwares installed on a windows-latest vm hosted image on Azure DevOps. And it turns out that cosmos db emulator is pre installed. Docs here – https://github.com/actions/virtual-environments/blob/main/images/win/Windows2019-Readme.md.
    It would be good to just put a note in the article that emulator is preinstalled on the windows-latest image of azyure devops hosted vm images. Just my observation.

    Thanks.

    1. Hi Lohith thanks for the feedback! Updated.

      Greetings and thanks Damien

      1. gald i could be of help 🙂

  4. […] Integration Testing for ASP.NET CORE Using EF Core Cosmos With Xunit and Azure DevOpsDamien Bod shows us how integration tests can be implemented for those using Cosmos DB and EF Core. […]

  5. […] Integration Testing for ASP.NET CORE Using EF Core Cosmos With Xunit and Azure DevOps Damien Bod shows us how integration tests can be implemented for those using Cosmos DB and EF Core. […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: