How do I add a DbContext to an MSTest project? - c#

I trying to test some code that uses Entity Framework, but I can't figure out how to reference the EF Context classes from the separate MSTest project. Both projects are in the same solution.
Cannot convert lambda expression to type 'DbContextOptions' because it is not a delegate type
In my Test case:
[TestClass]
public class GreenCardUserTest
{
[TestMethod]
public void TestAddUser()
{
// REFERENCE TO OTHER PROJECT. WORKS FINE
AppUserViewModel a = new AppUserViewModel();
//LIKELY INCORRECT attempt to duplicate code from Startup.cs in other project
using (GreenCardContext _gc = new GreenCardContext(options => options.UseSqlServer(Configuration.GetConnectionString("MyConnection"))))
{
new GCLandingUserModel().AddUser(a,_gc);
}
}
}
Excerpt from main project Startup.cs (which works fine):
services.AddDbContext<GreenCardContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyConnection")));

I would suggest using InMemoryDatabase. In your test class, use [TestInitialize] to setup your dummy database:
[TestClass]
public class GreenCardUserTest
{
private readonly context;
[TestInitialize]
public Setup()
{
DbContextOptions<GreenCardContext> options;
var builder = new DbContextOptionsBuilder<GreenCardContext>();
builder.UseInMemoryDatabase();
var options = builder.Options;
context = new GreenCardContext(options);
}
[TestMethod]
public void TestAddUser()
{
// user context here...
}
}

What you have to do is:
1) Add a reference in your test project to your context's project (if you haven't already)
2) Add references to Entity Framework to your test project
3) Add an appconfig to your test project and set entity framework config on it. Your test will read the configuration from it's own config, not your app's. Very useful as you can, by example, use dblocal and codefirst in tests and sqlserver when on running :)
You have done some of this, I think the point you are missing is the third :)

The code that you have from Startup.cs is using a delegate to tell your application how to build your DbContext at runtime.
However in your test, you need to actually provide an instance of DbContextOptions, not just a delegate. To do this, you can use DbContextOptionsBuilder:
var options = new DbContextOptionsBuilder<GreenCardContext>()
.UseSqlServer(Configuration.GetConnectionString("MyConnection"))
.Options;
using (GreenCardContext _gc = new GreenCardContext(options))
{
new GCLandingUserModel().AddUser(a,_gc);
}
Also, if you do insist on unit testing your DbConext, you may want to look into using InMemoryDatabase so that you don't need an open SQL connection in your tests. See this document for more details.

Related

EF Core DbContext Concurrency Issue with XUnit Tests

I seem to have a strange concurrency issue I can't seem to put my finger on.
When I construct my implementation of a DbContext I inject the Entities I want to be built by the modelbuilder (don't worry about why). This is only done once by my app at runtime and runs fine, but when I'm integration testing DB integration, I inject only the test entities I need for my InMemoryDatabase.
Now I seem to have a weird issue where two unit tests in different class files needing different entities seem to get crossed over.
I keep running the unit tests and the first test will pass, but the second test will fail saying that TestObjectB doesn't exist in the model. When I inspect the model it says TestObjectA exists instead, even though it wasn't injected on this test. So as if the implementation of the DataContext was static and overwritten...These are different files and fresh constructors for the context, I don't understand how they are crossing paths? If I run the unit test that fails on it's own, it passes.
Note the following code has been simplified for your view.
DB Context:
public class DataContext : DbContext
{
private readonly List<IEntity> _entities;
public DataContextA(List<IEntity> entities, DbContextOptions options) : base(options)
{
_entities = entities;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entity in _entities)
{
modelBuilder.Entity(entity.GetType());
}
}
}
Test Implementation 1:
[Fact]
public void CheckUniqueFieldA()
{
var options = new DbContextOptionsBuilder<DataContext>();
options.UseInMemoryDatabase(Guid.NewGuid().ToString());
using (var context = new DataContext(new List<IEntity> { new TestObjectA() }, options.Options))
{
//Do Something
}
}
Test Implementation2:
[Fact]
public void CheckUniqueFieldB()
{
var options = new DbContextOptionsBuilder<DataContext>();
options.UseInMemoryDatabase(Guid.NewGuid().ToString());
using (var context = new DataContext(new List<IEntity> { new TestObjectB() }, options.Options))
{
//Do Something
}
}
The reason is EF Core model caching described in Alternating between multiple models with the same DbContext type documentation topic:
...the model caching mechanism EF uses to improve the performance by only invoking OnModelCreating once and caching the model.
By default EF assumes that for any given context type the model will be the same.
The link also contains an example how to solve it. You'd need to create custom implementation of IModelCacheKeyFactory interface and replace the default EF Core implementation using ReplaceService inside OnConfiguring. The implementation should return an object representing the unique cache key for a given DbContext instance. The default implementation simply returns context.GetType().

Using dependency injection in a unit test class

I am using xunit to write unit tests for my web api. My web api uses dependency injection to pass along a DbContext and an IConfiguration as parameters using constructor injection. I would like to be able to do this in my unit tests project so that I can easily have access to the DbContext and IConfiguration. I have read about using a fixture to do this but I have not found a good example on how this would be handled. I have seen articles using the TestServer class but my project targets the .NETCoreApp1.1 framework which won't let me use the TestServer class. Any suggestions here?
Are you sure that you need to use those dependencies in your tests?
According to unit testing philosophy consider using some mock frameworks to provide dummy instances of your DbContext and IConfiguration with suitable behavior and values.
Try to look into NSubstitute or Moq framework.
The easiest way i've found to create a 'fake' configuration to pass into methods requiring an IConfiguration instance is like this:
[TestFixture]
public class TokenServiceTests
{
private readonly IConfiguration _configuration;
public TokenServiceTests()
{
var settings = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("JWT:Issuer", "TestIssuer"),
new KeyValuePair<string, string>("JWT:Audience", "TestAudience"),
new KeyValuePair<string, string>("JWT:SecurityKey", "TestSecurityKey")
};
var builder = new ConfigurationBuilder().AddInMemoryCollection(settings);
this._configuration = builder.Build();
}
[Test(Description = "Tests that when [GenerateToken] is called with a null Token Service, an ArgumentNullException is thrown")]
public void When_GenerateToken_With_Null_TokenService_Should_Throw_ArgumentNullException()
{
var service = new TokenService(_configuration);
Assert.Throws<ArgumentNullException>(() => service.GenerateToken(null, new List<Claim>()));
}
}
[This obviously using NUnit as the testing framework]

Add test data in integration tests

I'm writing integration tests for my .NET Core app using xUnit. I have various things I want to test, but for brevity I'll use this example. I need to test these things ("document" is just a string, I'm not dealing with files):
Register new employee
Log in as employee
Add a new document for the employee
Add a comment to the document
Tests are of course supposed to run independently of each other, but I cannot add a comment to a nonexistent document. I need the document to exist to test adding a comment, but the employee must also exist to add the document.
I have test classes written for all entities I want to use, but I don't know how to actually get them in the database for a given test. How can I "seed" the DB for a given test?
TestStartup.cs:
public class TestFixture : IDisposable
{
readonly TestServer _server;
public HttpClient _client { get; }
public TestFixture()
{
var builder = new WebHostBuilder()
.UseContentRoot(#"..\..\..\..\..\src\MyProj.Web")
.UseEnvironment("IntegrationTestDevelopment")
.UseStartup<Web.Startup>();
_server = new TestServer(builder);
_client = _server.CreateClient();
_client.BaseAddress = new Uri("http://localhost:2856");
}
public void Dispose()
{
_client.Dispose();
_server.Dispose();
}
}
Relevant part of Web.Startup:
services.AddEntityFrameworkInMemoryDatabase()
.AddDbContext<MyContext>(options => options.UseInMemoryDatabase());
Example test class:
public class EmployeeTests : IClassFixture<TestFixture>
{
public HttpClient _client;
public EmployeeTests(TextFixture fixture)
{
_client = fixture._client;
}
[Theory]
[MemberData("ValidRegistrations")]
public async Task Register_new_employee_succeeds(EmployeeModel model)
{
var request = Helpers.CreateRequestMessage("POST", "/api/employees", model);
var response = await _client.SendAsync(request);
Assert.Equal(HttpStatusCode.Created, response.StatusCode);
}
}
I may be missing something here (knowledge of, but never used, .Net Core), but as you say this is an integration test rather than a pure unit test, so would an alternative approach be:
Have the integration test merely test the call / connectivity to the service (i.e to test that 'employees' can be called I think in your scenario - regardless of database content)
Create a new test (ideally a unit test, but let's face it integration tests definately have a place in this world) to test the functionality of the 'employees' method / function
If the above isn't an option for you, how about considering seeding the db in the test class (one of the method or class set up methods - I forget their names in xUnit) by calling simple database interaction code, doesn't have to be anything exotic or fancy if it's just a seed followed by a subsequent end-of-test tear down. A basic db connection and query execution would presumably suffice.
Appreciate this doesn't directly answer your question, more of an outsiders viewpoint.

EF 6.1 how to swap migration configurations for testing?

I am trying to use Integration testing using EF 6.1 and run into a problem that my migration configuration settings are used where I dont need them. And I cant figure out how to swap them out for testing.
Here is my Test Class:
[TestClass]
public class SXSeasonConverterTests
{
public void RecreateDatabaseForTesting()
{
Database.SetInitializer(new TestDatabaseSeedingInitializer());
using (var context = new BaseNFLContext("NFLContextIntegrationTests"))
{
context.Database.Initialize(true);
}
}
public SXSeasonConverterTests()
{
RecreateDatabaseForTesting();
}
}
Here is my Initializer class:
public class TestDatabaseSeedingInitializer : DropCreateDatabaseAlways<BaseNFLContext>
{
protected override void Seed(BaseNFLContext context)
{
//Add Teams
context.Teams.Add(new Team { Code = "ARZ", Name = "Arizona Cardinals" });
context.Teams.Add(new Team { Code = "ATL", Name = "Atlanta Falcons" });
...
}
}
However when I try to run the test, I get the error that my AutomaticMigrations are disabled. When I looked further I found that It uses this code on Initialize:
internal sealed class NFLConfiguration : DbMigrationsConfiguration<BaseNFLContext>
{
public NFLConfiguration()
{
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = false;
}
}
This code is obviously there for production. However when doing testing how can I swap those migration configurations and set AutomaticMigrationsEnabled = true;?
I used to test my EF stuff using a special unittesting database and executed the tests in a TransactionScope which was rolled back at the end of the test. This way, no data was actually stored in the database.
I wasn't fast, but it suited our purpose.
You should create a separate project for testing and have a separate Db context that points to a test database. You can create something like a IDbContext interface that tells you which object models need to be tested. Also, the data access layer needs to allow you to inject this test Db context as a dependency.

In a Visual Studio package, can I simulate (DTE) GetService(typeof (DTE)) for tests?

In my package I am using (DTE) GetService(typeof (DTE)) to get information about the currently opened solution. Is there a way to simulate this for a test, particularly so that I can build using dte.Solution.SolutionBuild?
Code in main package class:
var solutionModel = new SolutionModel(((DTE) GetService(typeof (DTE))).Solution);
SolutionModel class (stripped back):
public class SolutionModel
{
private readonly Solution _packageSolution;
public SolutionModel(Solution solution)
{
_packageSolution = solution;
}
public SolutionModel() {} // This constructor is used for tests so _packageSolution will be null
public bool Build()
{
if (_packageSolution != null)
{
var buildObject = _packageSolution.SolutionBuild;
buildObject.Build(true);
return buildObject.LastBuildInfo == 0;
}
return ManualCleanAndBuild(); // current messy alternative way of doing the build for tests
}
}
So I want to be able to use the _packageSolution build rather than ManualCleanAndBuild() in my tests.
Assuming that you are referring to integration tests (and not to unit tests) where you need to load your package in a real Visual Studio instance, it depends on the testing framework that you are using. If you are using MSTest with the VSIDE Host Adapter (the integration test project that the package wizard creates if you mark the checkbox in the last page of the wizard) there is a Utils.cs file that uses the static VsIdeTestHostContext class to get the DTE instance or services:
public static class VsIdeTestHostContext
{
[CLSCompliant(false)]
public static DTE Dte { get; }
public static IServiceProvider ServiceProvider { get; set; }
}
If you want to learn the inners of the VS IDE Host Adapter I think that the VS 2008 SDK was the last SDK that provided the source code and the documentation (http://msdn.microsoft.com/en-us/library/bb286982%28v=vs.90%29.aspx)
The way I ended up 'solving' this was to mock EnvDTE.Solution instead (seems like it can only be done in the Package_IntegrationTests project which is created for you - you can't reference EnvDTE in any other project). I couldn't figure out how to use the methods in Utils.cs as suggested by Carlos below to open my existing solutions.

Categories