Testing Entity Framework Core Correctly in .NET

Nick Chapsas
1 Jul 202408:03

Summary

TLDRIn this video, Nick discusses the common mistake of using an in-memory database for integration tests with EF Core, which can lead to unreliable tests and missed critical application flow issues. He demonstrates a simple API example and shows how replacing the real database provider with an in-memory version during testing can be problematic. Nick then presents a solution using test containers to ensure tests are run against a real database, maintaining the integrity of the integration testing process. The video also promotes a new course on messaging in .NET with MassTransit, highlighting its importance for developers.

Takeaways

  • 🚫 Avoid using the in-memory provider for integration tests with EF Core as it does not accurately test the database integration.
  • 🔍 Integration tests should test the critical component of the application's flow, including the conversion of the DbContext code to the database queries.
  • 💡 The in-memory provider can lead to flaky tests that do not reflect the real-world failures of the application.
  • 🛠️ Demonstrated a simple customers API with integration tests, showing how to properly set up and run tests.
  • 🔗 Highlighted the importance of injecting the app DbContext into services to ensure that the database context is tested correctly.
  • 📝 Discussed two schools of thought on validating tests: validating API responses and validating database objects directly.
  • 🔄 Showed how to override the configure web host in tests to replace the real database provider with the in-memory provider.
  • 📚 Provided a step-by-step guide on how to add the in-memory package and configure services to use it for testing.
  • 🚀 Introduced Testcontainers as a solution to use the real database in integration tests by running a containerized version of the database.
  • 🛑 Explained how to configure Testcontainers to start and stop a database container asynchronously for each test run.
  • 🔄 Showed how to pass the connection string from the Testcontainers database to the application for real database testing in CI/CD pipelines.

Q & A

  • What is the single biggest mistake people make when writing integration tests for an application using EF Core?

    -The biggest mistake is replacing the real database provider with the in-memory version during testing. This can lead to flaky tests that do not accurately represent the application's behavior with the actual database.

  • Why are integration tests that use the in-memory provider not considered good tests?

    -They are not good because they remove the critical component of the application's data flow, which includes the conversion of the DbContext code and query generations, leading to tests that do not fail in the same way the application would in real scenarios.

  • What is the purpose of the video presented by Nick?

    -The purpose of the video is to explain the common mistake made in EF Core integration testing and to show how to solve the problem by using real databases with the help of test containers.

  • What is the role of the DbContext in the integration tests discussed in the video?

    -The DbContext is a critical component that needs to be tested as it is responsible for the conversion of the application's data flow to what the database accepts. Testing it with an in-memory provider does not accurately reflect its behavior with a real database.

  • How does Nick demonstrate the mistake in the integration tests?

    -Nick demonstrates the mistake by showing how developers replace the real database provider with an in-memory provider in their integration tests, which leads to unreliable tests.

  • What is the alternative solution to using an in-memory provider for integration tests presented in the video?

    -The alternative solution is to use test containers to run integration tests against a real database, ensuring that the tests are more reliable and accurately represent the application's behavior.

  • What is the significance of using the same database as the production environment in integration tests?

    -Using the same database ensures that the tests are testing the actual integration points of the application, including query generations and database interactions, which are crucial for identifying potential issues before deployment.

  • How does Nick use test containers to run integration tests with a real database?

    -Nick uses test containers by installing a Postgres container and configuring it to work with the application's DbContext. This allows the integration tests to run against a real database environment.

  • What is the benefit of using Docker containers in the CI pipeline for integration tests?

    -Docker containers in the CI pipeline ensure that the integration tests run in a consistent and isolated environment, similar to production, which helps in identifying issues early in the development cycle.

  • What is the course being promoted in the video, and what does it cover?

    -The course being promoted is 'From 0 to Hero Messaging in .NET with MassTransit'. It covers advanced concepts and production-ready patterns for messaging in .NET, including the Outbox pattern and the Saga pattern, using MassTransit, a popular library for messaging in .NET applications.

  • How does Nick ensure that the integration tests are not just functional tests but also test the integration points?

    -Nick ensures this by using test containers to run the tests against a real database, which includes the actual database interactions and query generations, making the tests more meaningful and representative of the application's behavior.

Outlines

00:00

🚫 Avoiding Flaky Integration Tests with EF Core

In this video, Nick discusses a common mistake made when writing integration tests for applications using Entity Framework Core (EF Core). He emphasizes the importance of not replacing the real database provider with an in-memory version during testing, as this can lead to unreliable tests that do not accurately reflect the application's behavior with the actual database. Nick demonstrates this with a simple customers API example, showing how tests might pass with an in-memory provider but fail with the real database. He also explains the significance of testing the DB context code and query generation as part of the integration process. The video includes a walkthrough of a flawed test setup and a correct approach using real database testing.

05:00

📚 Leveraging Test Containers for Reliable Integration Testing

The second paragraph of the video script introduces a course on messaging in .NET with MassTransit, a popular library for messaging. The course promises to cover both basic and advanced concepts, including production-ready patterns like the Outbox pattern and the Saga pattern. Nick then transitions back to the main topic, explaining how to properly conduct integration tests using test containers with a real database. He details the process of setting up a test container for PostgreSQL, configuring it with the web application factory, and ensuring that the tests run against a real database instance. This approach ensures that the tests are reliable and accurately reflect the application's behavior with the actual database, which is crucial for maintaining code quality and reliability in continuous integration pipelines.

Mindmap

Keywords

💡Integration Test

Integration testing is a type of software testing where individual software modules are combined and tested as a group to ensure that they work together as expected. In the video, the main theme revolves around the common mistake made during integration testing for applications using Entity Framework Core (EF Core), emphasizing the importance of not replacing the real database provider with an in-memory version during these tests.

💡EF Core

Entity Framework Core (EF Core) is an open-source, lightweight, and extensible version of the Entity Framework data access technology provided by Microsoft. It serves as an Object-Relational Mapper (ORM) for .NET applications, allowing developers to work with a database using .NET objects. The video discusses the pitfalls of using EF Core in integration tests by swapping out the real database with an in-memory provider.

💡In-Memory Database

An in-memory database is a database that resides in the main memory (RAM). It is typically faster than a disk-based database system but is volatile, meaning data is lost when the system is shut down. In the context of the video, the in-memory provider is used as a substitute for real databases in tests, which the speaker argues is a mistake because it omits the actual database behavior from the testing process.

💡Flaky Tests

Flaky tests are tests that sometimes pass and sometimes fail under the same conditions without any changes to the code. They are unreliable and can lead to false positives or negatives. The video script mentions that using an in-memory provider for EF Core in integration tests can result in flaky tests because the tests do not accurately reflect the behavior of the real database.

💡Database Context

In the context of EF Core, a database context is an object that represents a session with the database and allows you to query and save instances of your entities. The video script explains that the mistake people make is injecting a real database context in the application's services and then replacing it with an in-memory context in integration tests, which does not test the critical component of the application's data flow.

💡Query Generation

Query generation is the process by which an ORM like EF Core translates LINQ queries into SQL statements that can be executed against a database. The video points out that changing the database provider to an in-memory version in tests removes the query generation aspect of the integration point, which is a significant part of what should be tested.

💡Web Application Factory

A web application factory is a design pattern used in ASP.NET Core applications to create a test harness for integration testing. It allows for the setup and teardown of a full web application environment for each test. In the video, the speaker uses the web application factory to demonstrate how tests are typically configured to use an in-memory provider instead of a real database.

💡DI Container

DI Container stands for Dependency Injection Container, a software design pattern used to manage dependencies between classes. In the video, the speaker explains how to use the DI container in the web application factory to replace the real database provider with an in-memory provider for testing purposes, which is the mistake being discussed.

💡Test Containers

Test containers are a concept where a real database is run in a container for the purpose of testing. This allows tests to interact with a real database environment without affecting the production database. The video script suggests using test containers with a real database, such as PostgreSQL, to create reliable and accurate integration tests.

💡Docker

Docker is a platform that allows developers to develop, deploy, and run applications in containers. Containers are lightweight, portable, and can encapsulate an application with all its dependencies. In the video, Docker is mentioned as a tool to run test containers for integration testing with a real database environment.

💡CI Pipeline

CI Pipeline refers to a Continuous Integration pipeline, which is a set of automated steps that a developer goes through to contribute to a larger codebase. The video script mentions adding the use of test containers to a CI pipeline to ensure that integration tests run reliably with a real database in a continuous integration environment.

Highlights

The single biggest mistake made in integration testing with EF Core is replacing the real database provider with the in-memory version.

Using the in-memory provider for integration tests can lead to flaky tests and a failure to test the critical component of the application's flow.

Query generations are part of the integration point, and changing to in-memory removes the conversion, leading to unreliable tests.

The video will explain the problem and demonstrate a solution to avoid using the in-memory provider for integration tests.

A simple customers API is used as an example to illustrate the integration testing process.

The importance of injecting an app DB context into the customer service is emphasized for proper integration testing.

A common mistake is to replace the real database connection string with an in-memory provider in the program.cs file for integration tests.

The video shows two schools of thought on validating tests: one through API response and the other by directly querying the database.

The video demonstrates how to override the configure web host to replace the real provider with the in-memory one for testing.

Instructions on adding the Microsoft.EntityFrameworkCore.InMemory package for using the in-memory database are provided.

The process of removing the real database configuration and adding the in-memory database configuration is shown.

Debugging the integration tests with breakpoints at the controller and service levels is demonstrated.

The video points out that using in-memory for integration tests is more of a functional test rather than a true integration test.

A new course on messaging in .NET with MassTransit is announced, highlighting its importance and offering a discount.

The solution to use real databases for integration tests involves using test containers with the appropriate database technology.

Instructions on configuring test containers with PostgreSQL and integrating them into the Web Application Factory are given.

The video shows how to start and stop a container asynchronously for testing and how to pass the connection string from the container.

The importance of adding test container configuration to CI pipelines for reliable integration tests is discussed.

The video concludes by inviting viewers to share their testing experiences and thanking them for watching.

Transcripts

play00:00

hello everybody I'm Nick in this video

play00:01

I'm going to talk about the single

play00:03

biggest mistake that people make when

play00:05

they write integration test for an

play00:06

application that is using EF core and

play00:09

that is replacing the real database

play00:11

provider during testing with the

play00:13

inmemory version if you have integration

play00:15

test right now on EF core and you

play00:18

replace whatever you're using post

play00:20

equals server Cosmos DB and you replace

play00:23

that with inmemory provider you don't

play00:25

have integration tests or at least you

play00:27

don't have good integration tests those

play00:30

things are flaky those things are bad

play00:32

and they're not testing a critical

play00:34

component of your application's flow

play00:36

which is the conversion of your DB

play00:38

context code and all that link and

play00:40

everything you've written there to

play00:41

whatever your database accepts those

play00:43

query Generations are part of your

play00:46

integration point and changing that to

play00:48

the inmemory is a massive no no because

play00:51

you completely remove that conversion

play00:53

which leads to very very flaky tests and

play00:55

your integration tests won't fail in the

play00:58

same way your application will fail when

play01:00

the time comes for it to fail so in this

play01:02

video I'm going to explain all that and

play01:03

I will show you how you can solve that

play01:05

problem very very easily so let me show

play01:07

what I have here I have a simple

play01:09

customers API over here and I also have

play01:11

an integration test project which does

play01:14

have integration tests so what I'm going

play01:16

to do is just first run the API to show

play01:18

you what I have here so the API is

play01:20

running I'll go to insomnia and I'll go

play01:22

and create a customer so let's say I

play01:23

want to create at Nick chaps over here

play01:26

so customer has been created and I can

play01:28

actually see that the customer has been

play01:29

created because I can see the customer

play01:32

over here they can retrieve the customer

play01:34

updated deleted and so on and let's say

play01:37

we wanted to write integration tests for

play01:39

something like this I'll just delete

play01:40

this customer first and show you what I

play01:42

have in my services because the most

play01:44

important part is that I'm injecting an

play01:47

app DB context into the customer service

play01:50

and then I have all of my n framework

play01:51

core code here so the mistake that

play01:53

people make is that they say oh in your

play01:55

program.cs you register your real

play01:58

postest connection string and all that

play02:01

what you're going to do in your

play02:02

integration test is you're going to

play02:03

replace that with your inmemory provider

play02:06

so what I'll do is I'm going to go to my

play02:08

test here I have two tests testing the

play02:10

exact same thing the reason for that is

play02:11

because the sort of two schools of

play02:13

thought when it comes to validating your

play02:15

test one says that you can simply

play02:17

validate the response that you get from

play02:19

the API because those are integration

play02:21

tests using the web application Factory

play02:24

but there's another one that says you

play02:25

should go all the way down to your

play02:27

database get that object you created and

play02:29

validated that way what I did is I added

play02:32

both you can choose to do whatever you

play02:33

want I don't want to have the discussion

play02:35

to be focused on that that's why I'm

play02:37

covering both approaches the main thing

play02:39

is that people would go into the web

play02:41

application Factory and they would do

play02:43

the following they would say override

play02:45

the configure web host which gives you

play02:47

access to the web host Builder and now

play02:49

you can say Builder do configure

play02:51

services or configure test services and

play02:54

what this does is it gives you access to

play02:56

the DI container which means I can

play02:58

actually say remove the real provider

play03:01

now because it's time for me to run my

play03:03

tests and use the inmemory one how can I

play03:06

do that well first you need to add the

play03:08

microsoft. framework core. inmemory

play03:11

package over here by the way all this

play03:13

code is in the description down below

play03:15

and then once you do that to wire entty

play03:17

framework to use the inmemory version

play03:18

you have to first remove the

play03:20

configuration of postgress so what you

play03:23

would say is get the single registration

play03:26

so service do service type equals type

play03:30

of the DB context options of that DB

play03:34

context so DB context that's enough you

play03:36

don't need to remove the DB context all

play03:38

you need to remove is the options the

play03:40

configuration and then you can say add

play03:43

DB context over here app DB context and

play03:46

you can say that configure this to use

play03:48

the inmemory database I'm going to give

play03:51

it a name I'm just going to say tests

play03:53

and that's it and now if I go ahead and

play03:54

I kill my database so database is not

play03:57

running as you can see connection

play03:58

refused if I go ahead and I run my

play04:01

integration test which I have to so if I

play04:03

run it they're both going to pass and

play04:06

this actually does work I'm going to

play04:07

just go ahead and stick a break point

play04:09

over here and let's stick a break point

play04:11

on the controller level as well over

play04:13

here so if I go back to my test and I

play04:16

say go ahead and debug it then you'll

play04:18

see and I'm going to need another break

play04:20

point here too then you'll see it's

play04:22

coming here it's going to the API making

play04:25

that call using the we application

play04:26

Factory the request comes in creates the

play04:29

user retener response all that using the

play04:31

inmemory DB context then comes back gets

play04:34

the response customer was created on the

play04:37

response level but we also want to check

play04:38

the database so I have access to the DB

play04:40

context which as you can see over here

play04:43

is the inmemory one so I'm going to say

play04:46

just select everything again database is

play04:48

not running but I'm getting a response

play04:50

and I'm validating the problem is that

play04:52

this is more of a functional test this

play04:54

doesn't actually test my integration

play04:55

point now before I move on I'd like to

play04:57

let you know that we just launched a

play04:58

brand new course on home train call

play05:00

from0 to Hero messaging in.net with mass

play05:03

transit and is is an amazing 6 and 1

play05:05

half hour course by Arena SCU will teach

play05:07

you everything you need to know about

play05:09

Q's pubsub messages but also show you

play05:12

how you can use it with M Transit which

play05:14

is the most popular library for doing

play05:16

messaging in net by far messaging exists

play05:20

in basically every single application

play05:21

and if you join a company it is very

play05:24

very likely they will be using mass

play05:25

transit so you must know both Concepts

play05:28

very very well Anda want only teach you

play05:30

the basics but you will also go into

play05:32

very very Advanced production ready

play05:33

patterns such as the out books pattern

play05:35

The Saga pattern and so on the concept

play05:38

in the library is a must know for every

play05:40

single developer and to celebrate the

play05:41

launch the first 400 of you can use

play05:42

discount code Transit 20 at checkout to

play05:45

get 20% off now back to the video so how

play05:48

would you make this test be what it

play05:50

should be which is using the real

play05:51

database well with test containers so

play05:54

what you would do is you would say test

play05:56

containers and then choose a database

play05:58

technology that you use in this case is

play06:00

postgress I'm going to say install that

play06:02

and then I'm just going to configure

play06:03

slightly my iclass picture for the web

play06:06

application Factory so I'm going to say

play06:08

that this has an i async lifetime

play06:11

because I want to start and stop a

play06:12

container asynchronously so yes we are

play06:15

going to use Docker but don't worry your

play06:17

CI almost definitely supports Docker I'm

play06:20

going to say that this is actually a new

play06:22

method here we go and then I'm going to

play06:25

add my container so I'm going to say

play06:27

private read only postgress SQL

play06:30

container and that's going to be my

play06:33

database container and that's going to

play06:36

be a new post SQL Builder so I'm going

play06:38

to say with username workshop with

play06:42

password password which is what I have

play06:45

in the real application to and then with

play06:47

database my DB and I'm going to go ahead

play06:51

and just build that and once I do that I

play06:53

can go ahead and say hey just start this

play06:56

in the beginning so start a sync and in

play06:59

the end after all the tests are run you

play07:02

can go ahead and stop it asynchronously

play07:05

and as you're going to see no container

play07:07

is running over here we have no running

play07:09

containers but I can now go up here and

play07:11

say use

play07:14

npgsql and I will pass down the

play07:16

connection string from that container

play07:17

because it does give me a connection

play07:20

string as you can see over here it

play07:21

builds an appropriate connection string

play07:23

for that database technology and now if

play07:26

I go ahead and I say run all of my tests

play07:28

again nothing exist this but now we can

play07:30

see the container is starting and as you

play07:32

can see both of my test passed and they

play07:35

used the real database now for a CI

play07:38

scenario you have to add this into your

play07:40

CI pipeline but CI tools support Docker

play07:43

containers so it is something you can

play07:45

totally do it's something I've been

play07:46

doing for the past 3 years already for

play07:48

my CI as well it works like a charm and

play07:51

I have reliable integration test but now

play07:53

I want to know from you do you have any

play07:55

test related goes like this leave a

play07:57

comment down below and let me know well

play07:58

that's all I had for for video thank you

play08:00

very much for watching and as always

play08:01

keep coding

Rate This

5.0 / 5 (0 votes)

関連タグ
EF CoreIntegration TestingInMemory ProviderDatabase TestingAPI DevelopmentSoftware TestingDevOpsDocker ContainersCI PipelineBest Practices
英語で要約が必要ですか?