Why instantiate new DbContext for each step of test - c#

In the Entity Framework Core documentation on Testing with SQLite, the sample code instantiates a new DbContext for each step of a test. Is there a reason for doing this?
// Copied from the docs:
[Fact]
public void Add_writes_to_database()
{
// In-memory database only exists while the connection is open
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
try
{
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlite(connection)
.Options;
// Create the schema in the database
using (var context = new BloggingContext(options))
{
context.Database.EnsureCreated();
}
// Run the test against one instance of the context
using (var context = new BloggingContext(options))
{
var service = new BlogService(context);
service.Add("http://sample.com");
context.SaveChanges();
}
// Use a separate instance of the context to verify correct data was saved to database
using (var context = new BloggingContext(options))
{
Assert.Equal(1, context.Blogs.Count());
Assert.Equal("http://sample.com", context.Blogs.Single().Url);
}
}
finally
{
connection.Close();
}
}
// Why not do this instead:
[Fact]
public void Add_writes_to_database()
{
// In-memory database only exists while the connection is open
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
try
{
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlite(connection)
.Options;
// Create the schema in the database
using (var context = new BloggingContext(options))
{
context.Database.EnsureCreated();
var service = new BlogService(context);
service.Add("http://sample.com");
context.SaveChanges();
Assert.Equal(1, context.Blogs.Count());
Assert.Equal("http://sample.com", context.Blogs.Single().Url);
}
}
finally
{
connection.Close();
}
}
Why not instantiate the context once, and use that instance throughout the entire test method, as shown in the second code sample?

Because that's how contexts should be used. They should be created per request and disposed of.
One practical reason is to ensure that you're going back to the data source each time instead of just looking at state within the context.

Related

EF Core : Testing with InMemory Database has inconsistent behavior

I'm using the InMemory database to test my repository layer in my ASP .NET Core Web API application.
Thus I have an issue, in several tests, I setup data. But, with the same code, when I run the tests sometimes the data is present and sometimes it is not. I don't understand why.
I'm using XUnit Testing Framework.
Here is my test :
public class UserRepositoryTest
{
private ApplicationDbContext context;
void setup()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: "ApplicationDatabase")
.Options;
this.context = new ApplicationDbContext(options);
this.context.Database.EnsureDeleted();
}
[Fact]
void GetUserByUsernameTest()
{
this.setup();
// Given
var manager = new User {Username = "Ombrelin", Email = "test#test.fr"};
context.Users.Add(manager);
context.SaveChanges();
// When
var repo = new UserRepository(context);
var user = repo.GetUserByUsername("Ombrelin");
// Then
Assert.Equal("Ombrelin", user.Username);
}
[Fact]
void FindUsersByUsernameContainsTest()
{
this.setup();
// Given
var manager1 = new User {Username = "Arsène", Email = "test#test.fr"};
var manager2 = new User {Username = "Jean Michel", Email = "test#test.fr"};
context.Users.Add(manager1);
context.Users.Add(manager2);
context.SaveChanges();
// When
var repo = new UserRepository(context);
var users = repo.findUsersByUsernameContains("Ars");
// Then
Assert.Single(users);
}
Does anyone have a clue about this ?
Thanks in advance,
You are reusing the same database context across multiple tests. Tests may run in parallel. Thus, when using the same database context tests may influence each other's outcome. To avoid this, you need to isolate the tests by letting them use a clean database context:
public class UserRepositoryTest
{
[Fact]
public void GetUserByUsernameTest()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: $"ApplicationDatabase{Guid.NewGuid()}")
.Options;
using(var context = new ApplicationDbContext(options))
{
// Given
var manager = new User { Username = "Ombrelin", Email = "test#test.fr" };
context.Users.Add(manager);
context.SaveChanges();
// When
var repo = new UserRepository(context);
var user = repo.GetUserByUsername("Ombrelin");
// Then
Assert.Equal("Ombrelin", user.Username);
}
}
}
By appending a unique id to the database name, you are ensuring, that tests are using a unique in-memory database. Obviously, this will make test execution slower. A lot of testers also use different contexts for seeding the data and performing the test:
public class UserRepositoryTest
{
[Fact]
public void GetUserByUsernameTestSeparateContexts()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: $"ApplicationDatabase{Guid.NewGuid()}")
.Options;
using (var context = new ApplicationDbContext(options))
{
// Given
var manager = new User { Username = "Ombrelin", Email = "test#test.fr" };
context.Users.Add(manager);
context.SaveChanges();
}
using (var context = new ApplicationDbContext(options))
{
// When
var repo = new UserRepository(context);
var user = repo.GetUserByUsername("Ombrelin");
// Then
Assert.Equal("Ombrelin", user.Username);
}
}
}
This makes the tests more realistic, since functions, that are seeding the data, and functions, that are using the data, often use different contexts. Also keep in mind, that the InMemoryProvider is not a relational database. Therefore, it does not support some features of actual database servers such as referential integrity checks, TimeStamp, IsRowVersion and others. Check the MS documentation for details: here.

Get connection string value from Microsoft.AspNetCore.TestHost.TestServer

I have a test project, i need to get the connection string value in a test class
public class EmplyeesScenarios
{
private readonly TestServer _testServer;
private readonly AppDbContext _testService;
public EmplyeesScenarios()
{
_testServer = TestServerFactory.CreateServer<TestsStartup>();
var dbOption = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlServer("//here i need to put the same connection string of _testServer")
.Options;
_testService = new AppDbContext(dbOption);
}
}
You can use the service collection on your test host. For example:
var config = _testServer.Host.Services.GetRequiredService<IConfigurationRoot>();
var connectionString = config.GetConnectionString("Foo");
However, you should actually be using this for all your services, so instead of trying to new up your context at all, just do:
var context = _testServer.Host.Services.GetRequiredService<AppDbContext>();
No connection string needed.
Assuming that your test server is set up properly, you should just resolve your database context from the server instance directly. Like this:
_testServer = TestServerFactory.CreateServer<TestsStartup>();
// create service scope and retrieve database context
using (var scope = _testServer.Host.Services.CreateScope())
{
var db = scope.ServiceProvider.GetService<AppDbContext>();
// ensure that the db is created for example
await db.Database.EnsureCreatedAsync();
// add test fixtures or whatever
}
Technically, you could also resolve the configuration from the test host and the read the connection string out of it but since you are doing an integration test, you should actually test the full integration and not deviate from the existing setup by creating your database context manually.

Can't save list into Database using Entity Framework code first

This is a simple example for the problem i'm having in saving lists into my database.
[TestMethod]
public void InsertResultsIntoDatabase()
{
using (var context = new ResultContext())
{
DatabaseTestResults dbTestResults = new DatabaseTestResults();
dbTestResults.ZipFileName = "report-nominal - Copy.zip";
dbTestResults.testList.Add(1);
context.DbTestResults.Add(dbTestResults);
context.SaveChanges();
}
in this point the debugger will show that context contains the testList and the zipFileName correctly.
using (var context = new ResultContext())
{
var query = context.DbTestResults.Find("report-nominal -
Copy.zip");
}
when trying to get the information from the database it's saved only zipFileName and the list is empty.
How do I save lists into database?
Try adding context.Entry(dbTestResults).State = EntityState.Added before SaveChanges()
Like this:
using (var context = new ResultContext())
{
DatabaseTestResults dbTestResults = new DatabaseTestResults();
dbTestResults.ZipFileName = "report-nominal - Copy.zip";
context.DbTestResults.Add(dbTestResults);
context.Entry(dbTestResults).State = EntityState.Added
context.SaveChanges();
}

Entity Framework changing database of existing DbContext

If I have the following code (for example, in the constructor of my repository):
var db = new MyDbContext();
var entity = db.Set<Customer>();
Then later I do:
db.Database.Connection.ConnectionString = mySQLconnectionstring;
Do I need to 're-set' the entity?
Bad idea. You should create new context instance:
var db1 = new MyDbContext("connstr1");
var db2 = new MyDbContext("connstr2");
Otherwise you'll get more difficulties, than benefits you're supposing (if this ever possible). Note, that every context instance keeps local cache of materialized entities and tracks their changes.
Since the model is the same, model building (which is most significant performance hit in EF) will happen just once. I can't imagine, what else could force you to re-use context instances.
if you want to use same context for multiple database, you can do that.
one way is changing connection string of context in the memory.
before changing db that you want to use. Call this piece of code:
var connStr="YOUR_NEW_DB_CONNECTION_STRING_HERE";
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var connectionStringsSection = (ConnectionStringsSection)config.GetSection("connectionStrings");
connectionStringsSection.ConnectionStrings["YOUR_DB_CONNECTION_STRING_NAME_EG_CONN_STR"].ConnectionString = connStr;
connectionStringsSection.ConnectionStrings["YOUR_DB_CONNECTION_STRING_NAME_EG_CONN_STR"].ProviderName = "System.Data.EntityClient";
config.Save();
ConfigurationManager.RefreshSection("connectionStrings");
Write new class file and add this method to your context
public partial class YourEntitities
{
public void SetDatabase(string dataBase)
{
string connString = Database.Connection.ConnectionString;
Regex rgx = new Regex(#"(?<=initial catalog=)\w+");
string newconnString = rgx.Replace(connString, dataBase);
//string = connString.Replace("{database}", dataBase);
Database.Connection.ConnectionString = newconnString;
}
}

EF 5, Code First - Create a new database and run all migrations programmatically

I'm using Entity Framework Code First migrations, and I have a scenario where I want to run a suite of integration tests. Each time the tests run, I want to re-create the database, and apply all migrations
The steps should be:
Drop the existing test database (if any)
Create a new test database, and apply all migrations
Seed data
This is an existing project that I've added migrations to, and I used the Enable-Migrations command to create an "InitialCreate" migration that contains code to add all the tables to my database.
The code in my custom IDatabaseInitializer is as follows:
public void InitializeDatabase(MyContext context)
{
//delete any existing database, and re-create
context.Database.Delete();
context.Database.Create();
//apply all migrations
var dbMigrator = new DbMigrator(new Configuration());
dbMigrator.Update();
//seed with data
this.Seed(context);
context.SaveChanges();
}
The Up method of my InitialCreate migration is not getting called by this code, which is not what I expected. Instead, all of the tables are created when the Database.Create() method is called. I need the InitialCreate migration to run because I have additional code in there to create stored procedures.
So my questions is, how do I programmatically create a new database and run all migrations (including the InitialCreate migration)?
The following code has allowed me to meet the needs of my integration testing scenario outlined in the question, but surely there's a better way?
public void InitializeDatabase(MyContext context)
{
//delete any existing database, and re-create
context.Database.Delete();
var newDbConnString = context.Database.Connection.ConnectionString;
var connStringBuilder = new SqlConnectionStringBuilder(newDbConnString);
var newDbName = connStringBuilder.InitialCatalog;
connStringBuilder.InitialCatalog = "master";
//create the new DB
using(var sqlConn = new SqlConnection(connStringBuilder.ToString()))
{
using (var createDbCmd = sqlConn.CreateCommand())
{
createDbCmd.CommandText = "CREATE DATABASE " + newDbName;
sqlConn.Open();
createDbCmd.ExecuteNonQuery();
}
}
//wait up to 30s for the new DB to be fully created
//this takes about 4s on my desktop
var attempts = 0;
var dbOnline = false;
while (attempts < 30 && !dbOnline)
{
if (IsDatabaseOnline(newDbConnString))
{
dbOnline = true;
}
else
{
attempts++;
Thread.Sleep(1000);
}
}
if (!dbOnline)
throw new ApplicationException(string.Format("Waited too long for the newly created database \"{0}\" to come online", newDbName));
//apply all migrations
var dbMigrator = new DbMigrator(new Configuration());
dbMigrator.Update();
//seed with data
this.Seed(context);
context.SaveChanges();
}
private bool IsDatabaseOnline(string connString)
{
try
{
using (var sqlConn = new SqlConnection(connString))
{
sqlConn.Open();
return sqlConn.State == ConnectionState.Open;
}
}
catch (SqlException)
{
return false;
}
}
Just remove the "create database" step and use the migrations on their own. I put a sample project on GitHub, but the important bit is
Configuration config = new Configuration();
DbMigrator migrator = new DbMigrator(config);
foreach (string s in migrator.GetPendingMigrations())
{
migrator.Update(s);
}

Categories

Resources