Unit Testing Azure Functions With EF
Tags: csharp, designpatterns, azurefunctions, unittest
Published: 2023-06-24 12:00:00

## Introduction
In this post I demonstrate how to create and unit test an Azure function app that uses Entity Framework. I only debug the function app running on the local machine and never publish it to Azure, so no Azure subscription is required. This example uses Visual Studio 2022, .NET 6, Entity Framework 6.0.x, NUnit 3.x, localdb, and the azure functions tools with Visual Studio. The full example is [available in GitHub.](https://github.com/cwl157/unit-testing-azure-functions-with-ef)  
  
## Setup projects

There are 3 projects, the azure function, the data project that contains the data context and Entity Framework dependencies, and the unit test project. First, I'm going to create a shell of each project and then go into detail of each in the following sections. All 3 projects use .NET 6. To create the Azure Function project, open Visual Studio 2022 and search for the Azure Function template. If you don't see it in the list, you may need to install the Azure development features of Visual Studio.  
  

![create-azure-function-csproj](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/egrfdbv01sm0fwgoyywv.png)
  
Select next, give it a name, select create. I named mine **AzFuncUnitTestWithEf**. Choose Http Trigger and click create again. After the Azure function app is created, it is time to create the class library project for the data context. Right click on the solution and choose **Add / New Project.** Choose class library for the type and click **Next**. I named mine **AzFuncUnitTestWithEf.DataContext** but you can name it whatever you prefer.  
  
![class-lib-for-data-context-csproj](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pax0c4ea2mrdgttd9bgr.png)  
  
Finally, create the unit test project. I'm using NUnit, which has a template in Visual Studio 2022. I'm not sure if there are additional dependencies required for the template to be available. Right click on the solution and, once again, choose **Add / New Project**, search for **nunit**, and select **NUnit Test Project** from the list of templates.  
  
![create-nunit-csproj](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/72v2loec536jciouhcca.png)
  
Now that all projects are added, ensure the solution builds. In the next section, I'll go over creating the data context with Entity Framework and localdb.

## Entity Framework Setup

In this section, I'll detail adding the required NuGet dependencies for Entity Framework, and creating the model and data context. I do not cover a migration to create a real database because we're not using a real database in this example. For more information on EF migrations, visit [this Microsoft doc.](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli)  
  
The first step is to add the required NuGet dependencies. These are all installed to the **AzFuncUnitTestWithEf.DataContext** project. Open **Tools / NuGet Package Manager / Managed NuGet Packages for Solution**. Below is a table of the Entity Framework NuGet packages and which version to install.  

| NuGet Package                 | Version |
|-------------------------------|---------|
| Microsoft.EntityFrameworkCore | 6.0.4   |
| Microsoft.EntityFrameworkCore.SqlServer | 6.0.4 |

Next I'll create the model class and data context. This app uses a simple model class named book. The `Book.cs` file is below
```csharp  
public class Book
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    
    [Column(TypeName = "nvarchar(512)")]
    public string Title { get; set; }
    
    [Column(TypeName = "nvarchar(512)")]
    public string Author { get; set; }
    
    public DateTime PublishedDate { get; set; }
}
```
Create another class for the data context named `BookDataContext.cs`. This inherits from Entity Framework `DbContext` and contains a `DbSet` for Books.
```csharp
public class BookDataContext : DbContext
{
    public DbSet<Book> Books { get; set; }
    
    public BookDataContext(DbContextOptions<BookDataContext> options) : base(options)
    {
        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }
    }
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSqlServer(
                @"Server=(localdb)\mssqllocaldb;Database=book_db;Trusted_Connection=True;ConnectRetryCount=0");
        }
    }
}
```
At this point, build the project again to make sure the db model and context are correct. In the next section, I'll go over setting up the azure function start up class and using the data context in the azure function.  

## Add Function App StartUp

To use the data context in the Azure function, I'm going to inject it as a dependency. Like an ASP .NET core web app, an azure function can have a `StartUp` class that inherits from `FunctionsStartup` to configure services. Browse to **Tools / NuGet Package Manager / Manage NuGet Packages for Solution**. Install the NuGet packages below to the function app project to use the function startup class.  

| NuGet Package                 | Version |
|-------------------------------|---------|
| Microsoft.Azure.Functions.Extensions | 1.1.0   |
 
There is 1 method to implement, `void Configure(IFunctionsHostBuilder builder)`.We're adding 1 service and that is a `DbContext` for the `BookDataContext`. The `StartUp` class is below.
```csharp  
public class StartUp : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        var services = builder.Services;
        var configuration = builder.GetContext().Configuration;
   
        services.AddDbContext<BookDataContext>(options => options.UseSqlServer(configuration["DbConnection"]));
    }
}
```
Next, a `FunctionStartupAttribute` needs to be assigned in the `StartUp` class. Do this by adding this line above the namespace declaration: `[assembly: FunctionsStartup(typeof(StartUp))]`. We also need a database connection to configure the DB Context. This is referenced in the code above. I'm using local db and added a setting to the `local.settings.json` config file of `"DbConnection": "Server=(localdb)\\mssqllocaldb;Database=book_db;Trusted_Connection=True;MultipleActiveResultSets=true"`. We don't actually use this configuration in this demo but in a real project this would be the sql server connection string. The next step is to implement the function to actually do some work and I'll show that in the next section.  

## Azure Function Implementation

The next step is to implement the azure function to accept requests and save those requests to our database. Since we configured the `BookDataContext` as part of the services of the azure function, it can be injected into the azure function constructor through dependency injection. Add a private field and constructor to the azure function. Also, make the azure function class and Run method not static.
```csharp
private readonly BookDataContext _bookDataContext;
   
public Function1(BookDataContext ctx)
{
     _bookDataContext = ctx;
}
```
We can now reference `_bookDataContext` in the function run method. The function is an HTTP triggered function that accepts POST requests. The POST request contains Book information that is mapped from an input model to a database model and saved through the db context.
```csharp
[FunctionName("Function1")]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log, ExecutionContext executionContext)
{
    try
    {
        log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
    
        var book = await ParseInput(req);
  
        _bookDataContext.Add(Map(book));
    
        await _bookDataContext.SaveChangesAsync();
   
        return new NoContentResult();
    }
    catch (Exception ex)
    {
        log.LogError(ex.Message);
        return new InternalServerErrorResult();
    }
}
```
The following code is the `Parse` and `Map` private helper methods to round out the function class.
```csharp
private async Task<BookInput> ParseInput(HttpRequest req)
{
    string requestBody = String.Empty;
    using (StreamReader streamReader = new StreamReader(req.Body))
    {
        requestBody = await streamReader.ReadToEndAsync();
    }
    
    return System.Text.Json.JsonSerializer.Deserialize<BookInput>(requestBody);
}
    
private Book Map(BookInput b)
{
    return new Book { Title = b.Title, Author = b.Author, PublishedDate = b.PublishedDate };
}
```
The request is deserialized to a `BookInput` type. That class is below.
```csharp
public class BookInput
{
    public string Title { get; set; }
    public string Author { get; set; }  
    public DateTime PublishedDate { get; set; } 
}
```
That completes the azure function project. Finally, we're ready to setup a unit test that uses an in-memory DB Context and calls the `Run` method.

## Unit Test Implementation

The last step is to implement tests in the NUnit project that was created earlier. We'll first add a couple NuGet packages. Browse to **Tools / NuGet Package Manager / Manage NuGet Packages for Solution**. The table below lists the NuGet packages required for the test project.  

| NuGet Package                 | Version |
|-------------------------------|---------|
| Microsoft.EntityFrameworkCore.InMemory | 6.0.5   |
| Moq | 4.18.1 |

Once the packages are installed, create a new file named `FunctionTests.cs` that will contain our test. We're going to use an in-memory database with Entity Framework for our unit test. This exists for the lifetime that the tests are running. This allows tests to run against Entity Framework without a real database. A static instance of `InMemoryDatabaseRoot` is created for the database and a private field is used in the test class for `BookDataContext`.
```csharp
private static InMemoryDatabaseRoot _root = new InMemoryDatabaseRoot();
private BookDataContext _bookContext;
```
There is a `OneTimeSetup` method that is called once prior to all tests running that builds the in-memory database. This is done using a `DbContextOptionsBuilder` instance, like building any other database. The `UseInMemoryDatabase` extension method tells it to use an in-memory database. Finally, set the `_bookDataContext` field to a new `BookDataContext` using the db builder options. The one time setup method is below.
```csharp
[OneTimeSetUp]
public void OneTimeSetup()
{
    var dbBuilder = new DbContextOptionsBuilder<BookDataContext>();
    dbBuilder.UseInMemoryDatabase(databaseName: "Book", _root);
    _bookContext = new BookDataContext(dbBuilder.Options);
}
  ```
There is also a setup method that resets the database before each test run.
```csharp
[SetUp]
public void Setup()
{
    // Make sure database is empty when starting
    _bookContext.Database.EnsureDeleted();
    _bookContext.Database.EnsureCreated();
}
```
Now lets write a test that uses it. We'll write one test that calls the function's Run method and then verify that it wrote to the in-memory database. First, I'll explain all of the code and then list the full method. The test follows the Arrange, Act, Assert pattern. The arrange section creates the dependencies required for the function run method. This includes a Logger, an executionContext, and test request data.
  
The next step is to act by instantiating the Function class and passing the local `_bookContext` data context to the constructor. Next call the `Run` method passing in the local variables for the required parameters. This will execute the full Run method of the function.  
  
Finally, we'll assert the results. This is done by querying the in-memory database for the test Book that was just inserted by the function. Assert the book returned equals the test book data that was passed in the request. If everything checks out, we know the function inserted the book correctly into the database. The full code of the test method is below.
```csharp  
[Test]
public async Task ReturnsSuccess()
{
    // Arrange
    // Arrange function dependencies
    var loggerFactory = new LoggerFactory();
    var logger = loggerFactory.CreateLogger("DebugLogger");
    var executionContext = new Microsoft.Azure.WebJobs.ExecutionContext();
    var id = Guid.NewGuid();
    executionContext.InvocationId = id;
    // Arrange request data
    string input = "{\"Title\": \"Test Title\", \"Author\": \"Test Author\", \"PublishedDate\": \"2022-04-08T14:47:46Z\"}";
    using (var requestStream = new MemoryStream())
    {
        requestStream.Write(Encoding.ASCII.GetBytes(input));
        requestStream.Flush();
        requestStream.Position = 0;
 
        var requestMock = new Mock<HttpRequest>();
        requestMock.Setup(x => x.Body).Returns(requestStream);
    
        // Act
        Function1 sut = new Function1(_bookContext);
        var result = await sut.Run(requestMock.Object, logger, executionContext);
    
        Book resultData = await _bookContext.Books.FirstOrDefaultAsync(b => b.Title == "Test Title");
    
        //Assert
        Assert.AreEqual(typeof(NoContentResult), result.GetType());
        Assert.IsNotNull(resultData);
    }
}	
 ```   

## Conclusion

In conclusion, this post demonstrates how to unit test an azure function that uses entity framework with nUnit and an in-memory EF database. The key is the data context that connects to a database is injected as a dependency so different variations can be used. This allows the test to call the complete `Run` method and verify the results against an in-memory database, while the production code would inject a `_bookContext` connected to a live database.