Page 18 - MSDN Magazine, April 2017
P. 18
configure the provider for the context in the code where you con- figure other application-wide IoC services. Here’s an example that uses ASP.NET Core services in a typical startup.cs file:
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<SamuraiContext>(
options => options.UseSqlServer( Configuration.GetConnectionString("productionDb")));
services.AddMvc(); }
In this case, SamuraiContext is the name of my class that inher- its from DbContext. I’m using SQL Server again and I’ve stored the connection string in the ASP.NET Core appsettings.json file under the name productionDb. The service has been configured to know that any time a class constructor requires an instance of Samurai- Context, the runtime should not only instantiate SamuraiContext, but should pass in the options with the provider and connection string stated in this method.
When this ASP.NET Core app uses my SamuraiContext, by default, it will now do so with SQL Server and my connection string. But thanks to the flexibility I built into the SamuraiContext class, I can also create tests that use the same SamuraiContext but pass in a DbContextOptions object that specifies using the InMemory provider instead—or specifies any other options that are relevant to a particular test.
Deleting and recreating the SQL Server database might affect how long it takes to run the test.
In the next section I’ll show two different tests that involve EF Core. The first, Figure 1, is designed to test that the correct data- base interaction occurred. This means I truly want the test to hit the database, so I’ll construct DbContextOptions to use the SQL Server provider, but with a connection string that targets a test version of my database I can create and drop on the fly.
I use the EnsureDeleted and EnsureCreated methods to give me a totally fresh version of the database for the test, and these will work even if you don’t have migrations. Alternatively, you could use EnsureDeleted and Migrate to recreate the database if you have migration files.
Next, I create a new entity (samurai), tell EF to begin tracking it and then note the temporary key value the SQL Server provider supplies. After calling SaveChanges, I verify that SQL Server has applied its own database-generated value for the key, assuring me that this object was, indeed, inserted into the database correctly.
Deleting and recreating a SQL Server database might affect how long it takes to run the test. You could use SQLite in this case and get the same results more quickly while ensuring that the test is still hitting an actual database. Also, note that, like the SQL Server provider, SQLite also sets a temporary key value when you add an entity to the context.
If you have methods that happen to use EF Core but you want to test them without hitting the database, this is where the InMemory provider is so handy. But keep in mind that InMemory is not a
database and won’t emulate all flavors of relational database behavior—for example, referential integrity. When this is important to your test, you may prefer the SQLite option or, as the EF Core docs suggest, the SQLite in-memory mode as explained at bit.ly/2l7M71p.
Here’s a method I’ve written in an app that performs a query with EF Core and returns a list of KeyValuePair objects:
public List<KeyValuePair<int, string>> GetSamuraiReferenceList() { var samurais = _context.Samurais.OrderBy(s => s.Name)
.Select(s => new {s.Id, s.Name})
.ToDictionary(t => t.Id, t => t.Name).ToList(); return samurais;
I want to test that the method truly returns a KeyValuePair list. I don’t need it to query the database in order to prove this.
Following is a test to do that using the InMemory provider (which I’ve already installed into the test project):
[TestMethod]
public void CanRetrieveListOfSamuraiValues() {
_options = new DbContextOptionsBuilder<SamuraiContext>() .UseInMemoryDatabase().Options;
var context = new SamuraiContext(_options);
var repo = new DisconnectedData(context); Assert.IsInstanceOfType(repo.GetSamuraiReferenceList(),
typeof(List<KeyValuePair<int, string>>));
}
}
This test doesn’t even require any sample data to be available to the in-memory representation of the database because it’s enough to return an empty list of KeyValuePairs. When I run the test, EF Core will be sure that when the GetSamuraiReferenceList executes its query, the provider will allocate resources in memory for EF to execute against. The query is successful and so is the test.
What if I want to test that the correct number of results are returned? This means I’ll need to provide data to seed the InMemory provider. Much like a fake or mock, this requires creating the data and loading it into the provider’s data store. When using fakes and mocks, you might create a List object and populate that, then query against the list. The InMemory provider takes care of the container. YoujustuseEFcommandstopre-populateit.TheInMemorypro- vider also takes care of much of the overhead and extra coding that are needed when using fakes or mocks.
As an example, Figure 2 shows a method I’m using to seed the InMemory provider before my tests interact with it:
If my in-memory data is empty, this method adds in two new samurais and then calls SaveChanges. Now it’s ready to be used by a test. But how would my InMemory data store have data in it if I’ve just instantiated the context? The context is not the InMemory data store. Think of the data store as a List object—the context will
Figure 1 Testing That a Database Insert Works as Expected
[TestMethod]
public void CanInsertSamuraiIntoDatabase() {
var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlServer
("Server = (localdb)\\\\\\\\\\\\\\\\mssqllocaldb; Database = TestDb; Trusted_Connection = True; ");
using (var context = new SamuraiContext(optionsBuilder.Options)) { context.Database.EnsureDeleted(); context.Database.EnsureCreated();
var samurai = new Samurai();
context.Samurais.Add(samurai);
var efDefaultId = samurai.Id; context.SaveChanges(); Assert.AreNotEqual(efDefaultId, samurai.Id);
} }
14 msdn magazine
Data Points