Page 19 - MSDN Magazine, April 2017
P. 19
Figure 2 Seeding an EF Core InMemory Provider
I refactored my test class by removing the class-wide _options variable and the class constructor. Instead, I use a method for cre- ating the options for a named data store and seeding the particular
data store that takes in the desired name as a parameter:
private DbContextOptions<SamuraiContext> SetUpInMemory(string uniqueName) { var options = new DbContextOptionsBuilder<SamuraiContext>() .UseInMemoryDatabase(uniqueName).Options;
SeedInMemoryStore(options);
return options; }
I modified the signature and first line of the SeedInMemoryStore
to use the configured options for the unique data store:
private void SeedInMemoryStore(DbContextOptions<SamuraiContext> options) { using (var context = new SamuraiContext(options)) {
And each test method now uses this method along with a unique name to instantiate the DbContext. Here’s the revised CanRetrieve- AllSamuraiValuePairs. The only change is that I’m now passing in the new SetUpInMemory method along with the unique data store name. A nice pattern recommended by the EF team is to use the
test name as the name of the InMemory resource:
[TestMethod]
public void CanRetrieveListOfSamuraiValues() { using (var context = new
SamuraiContext(SetUpInMemory("CanRetrieveListOfSamuraiValues"))) { var repo = new DisconnectedData(context); Assert.IsInstanceOfType(repo.GetSamuraiReferenceList(),
typeof(List<KeyValuePair<int, string>>));
} }
Other test methods in my test class have their own unique data store names. And now you see there are patterns for using a unique set of data, or sharing a common set of data across test methods. When your tests are writing data to the in-memory data store, the unique names allow you to avoid side effects on other tests. Keep in mind that EF Core 2.0 will always require a name to be supplied, as opposed to the optional parameter in EF Core 1.1.
There’s one last tip I want to share about the InMemory data store. In writing about the first test, I pointed out that both the SQL Server and SQLite providers insert a temporary value to the Samurai’s key property when the object is added to the context. I didn’t mention that if you specify the value yourself, the provider won’t overwrite that. But in either case, because I’m using the default database behavior, the database overwrites the value with its own generated primary key value. With the InMemory provider, however, if you supply a key property value, that will be the value that the data store uses. If you don’t supply one, the InMemory provider uses a client-side key generator whose value acts as the data-store assigned value.
The samples I’ve used come from my EF Core: Getting Started course on Pluralsight (bit.ly/PS_EFCoreStart), where you can learn more about EF Core, as well as testing with EF Core. The sample code is also included as a download with this article. n
Julie lerman is a Microsoft Regional Director, Microsoft MVP, software team mentor and consultant who lives in the hills of Vermont. You can find her presenting on data access and other topics at user groups and conferences around the world. She blogs at thedatafarm.com/blog and is the author of “Programming Entity Framework,” as well as a Code First and a DbContext edition, all from O’Reilly Media. Follow her on Twitter: @julielerman and see her Pluralsight courses at juliel.me/PS-Videos.
Thanks to the following Microsoft technical expert for reviewing this article: Rowan Miller
private void SeedInMemoryStore() {
using (var context = new SamuraiContext(_options)) {
if (!context.Samurais.Any()) { context.Samurais.AddRange(
new Samurai { Id = 1,
Name = "Julie", },
new Samurai { Id = 2,
Name = "Giantpuppy", );
context.SaveChanges(); }
} }
create it on the fly, if needed. But once it’s been created, it remains in memory for the lifetime of the application. If I’m running a single test method, there will be no surprises. But if I’m running a num- ber of test methods, then every test method will use the same set of data and you may not want to populate it a second time. There’s more to understand about this, which I’ll be able to explain after you see a bit more code.
This next test is a bit contrived, but it’s designed to demonstrate using a populated InMemory store. Knowing that I’ve just seeded the memory with two samurais, the test calls that same GetSamuraiRefer- enceList method and asserts that there are two items in the resulting list:
[TestMethod]
public void CanRetrieveAllSamuraiValuePairs() {
var context = new SamuraiContext(_options);
var repo = new DisconnectedData(context); Assert.AreEqual(2, repo.GetSamuraiReferenceList().Count);
}
You may have noticed that I didn’t call the seed method or cre- ate the options. I’ve moved that logic to the test class constructor so I don’t have to repeat it in my tests. The _options variable is declared for the full scope of the class:
private DbContextOptions<SamuraiContext> _options;
public TestDisconnectedData() { _options =
new DbContextOptionsBuilder<SamuraiContext>().UseInMemoryDatabase().Options; SeedInMemoryStore();
Now that I’ve moved the seed method into the constructor, you might think (as I did) that it will get called only once. But that’s not the case. Did you know that a test class constructor gets hit by ev- ery test method that’s run? In all honesty, I had forgotten this until I noticed that tests were passing when run on their own, but failing when I ran them together. That was before I added in the check to see if the samurai data already existed in memory. Every method that triggered the seed method to be called would be seeding the same collection. This would happen whether I was calling the seed method in each test method or only once in the constructor. The check for pre-existing data protects me either way.
There’s a nicer way to avoid the problem of conflicting in-memory data stores. InMemory allows you to provide a name for its data store. If you want to move the creation of the DbContextOptions back into the test method, and do so for each method, specifying a unique name as a parameter of UseInMemory will assure that
each method is using its own data store.
msdnmagazine.com
}
April 2017 15