Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DeploymentItem breaks EntityFramework unit test in separate assembly

I have 5 Assemblies in my solution: A, B, B.Test, C and C.Test. B and C both reference A (and do not reference each other). B.Test references A and B, C.Test references A and C.

In B.Test, I am creating an EntityFramework6 DbContext object defined in B:

[TestMethod]
public void TestB() {
    MyBContext c = new MyBContext();
}

In C.Test, I have an empty unit test with a DeploymentItem:

[TestMethod]
[DeploymentItem("data.txt")]
public void TestC() { }

When I run the two tests separately, they both pass. HOWEVER, When I "Run All" both tests together as part of the same test run, TestB fails with the following exception:

"The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer' registered in the application config file for the ADO.NET provider with invariant name 'System.Data.SqlClient' could not be loaded. Make sure that the assembly-qualified name is used and that the assembly is available to the running application. See http://go.microsoft.com/fwlink/?LinkId=260882 for more information."

When I change TestC to comment out the DeploymentItem attribute as follows:

[TestMethod]
//[DeploymentItem("data.txt")]
public void TestC() { }

Both tests pass now. TestB creates the context without throwing an exception. Somehow, adding in the DeploymentItemAttribute in assembly C.Test is breaking a test which does not use DeploymentItem in a separate assembly B.Test (And I have other tests in C.Test that use DeploymentItem, so removing this one instance doesn't remove a reference from the library). It's taken me A LOT of time to even narrow the failure down this far, and I'm completely flummoxed about what to even do next to get this issue resolved.

EDIT: I found some information on MSDN which seems to resolve this issue (though I do not understand why).

  1. Running the unit tests through the Resharper test runner, or another test runner seems to resolve the issue. Only when I run the unit tests through VisualStudio (2012, if it matters) do the tests fail
  2. Adding the following code to my assembly which defines the DbContext seems to fix the issue:

    static MyDbContext () {
        var _ = typeof(System.Data.Entity.SqlServer.SqlProviderServices);
    }
    

    (The answer on MSDN suggests to add this to the context class itself, I added it to a Factory type for that context instead with the same beneficial results).

So it looks like we have an answer to "how do I make my unit tests work?" but not the question "why does this happen in the first place?" I suspect, given this solution, that EF6 is playing a little bit fast and loose with loading types and assemblies dynamically, and certain assemblies aren't being loaded when the tests are being executed from certain types of places.

like image 758
awhitworth Avatar asked Mar 28 '14 13:03

awhitworth


2 Answers

Problem is caused because the compiler doesn't output the EntityFramework.SqlServer.dll as it does not detect if it's used somewhere (it's only used through dependency injection). Simplest solution is to use one of the types of the assembly inside your test.

e.g. You can create a property or method (you don't need to use it, just exposing it as public is enough). To solve this problem I created a property inside a test helper:

public static System.Data.Entity.SqlServer.SqlProviderServices EnsureAssemblySqlServerIsCopied { get; set; }
like image 109
Geo Avatar answered Nov 08 '22 20:11

Geo


The reasons for this come from the loose coupling between EF and its providers together with the way MSTest runs tests.

With regards to providers, Entity Framework can work with a variety of different providers. The SQL Server provider is just one example. EntityFramework.dll does not have a hard dependency on the SQL Server provider assembly because it is not needed when using EF with any other provider.

This means that when using EF you should determine which provider(s) will be used and make sure that the provider assemblies are deployed along with your application or tests. This is usually done by installing the provider NuGet package into the application or test project. If the NuGet package is installed into the application/test project then the provider assembly will be placed into the bin folder regardless of whether or not it is referenced directly in the code. This is not the case if the provider NuGet package is only installed into some other project in the solution (such as a class library) rather than directly into the application/test project.

For historical reasons the SQL Server provider is shipped in the main Entity Framework NuGet package, so for the SQL Server provider it is the EntityFramework package that needs to be installed in the application/test project. If you don’t wish to install the provider/EntityFramework package into the application/test project then you can setup some other logic to ensure that the assembly is deployed.

Now with MSTest things become more tricky because it has its own deployment rules and configuration. This means that just installing the NuGet package may not be enough and you may need to setup specific MSTest deployment rules to ensure that the assembly is copied correctly. Another solution is to not use MSTest. On the EF team we switched from MSTest to xUnit some years ago and have had far fewer issues of this kind since doing so.

like image 26
Arthur Vickers Avatar answered Nov 08 '22 20:11

Arthur Vickers