DbContext.ChangeTracker throws SQLException in automated tests

I am writing automated tests for a project that I am working on in order to become better acquainted with MVC, EntityFramework (code first), unit testing and Moq.

I have a section in my Repository class that sets the LastModified field of my models whenever Repository.SaveChanges() is called by the controller, which works as follows (MyModelBase is the base class):

 public void RepoSaveChanges() { foreach(var entry in _dbContext.ChangeTracker.Entities().Where(e => e.State == EntityState.Modified)) { MyModelBase model = entry.Entity as MyModelBase; if (model != null) { model.LastModified = DateTime.Now; } } _dbContext.SaveChanges(); } 

This works great while the application is running in a normal online environment, but it interrupts when working in test methods. I use Moq to mock DbSets in DbContext and customize my test data.

Here's the weird part for me: My unit tests run normally (pass), but they never enter the foreach - it hangs when ChangeTracker.Entities() accesses it and completes the loop going to _dbContext.SaveChanges() . Mistake.

However, on another computer that shares the project with me, it gets a SQLException when ChangeTracker.Entities() is ChangeTracker.Entities() . I have SQLExceptions verified as thrown in VS2015 and there is no output or other indication of an exception on my side.

StackTrace Result:
in System.Data.ProviderBase.DbConnectionPool.TryGetConnection (DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionIntal

Message:
The test method MyProject.Tests.TestClasses.MyControllerTests.TestCreate threw an exception: System.Data.SqlClient.SqlException: a network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not available. Verify the instance name is correct and configure SQL Server to connect remotely. (provider: SQL network interfaces, error: 26 - server / instance location error)

Finally, my question is: is there a way to use Moq to scoff at ChangeTracker (I suspect not from the previous investigation), or is there another approach that I can apply to my RepoSaveChanges() that automatically sets the property? Without access to ChangeTracker.Entities() I will need the update logic to set the LastModified field for each type of model that has it. In the same way, I feel that I am avoiding using this API / part of the framework because testing is stubborn, not ideal.

Does anyone have an idea why a SQLException is not thrown / cannot be caught on my machine? Or any suggestions on using ChangeTracker.Entities() in unit tests? I would set the LastModified property individually for all my models and controllers as a last resort.

Update: Another example code was requested, so let me tell you more. I make fun of DbContext with moq, and then mock the DbSet objects contained in the DbContext:

 var mockContext = new Mock<MyApplicationDbContext>(); //MyApplicationDbContext extends DbContext Person p = new Person(); p.Name = "Bob"; p.Employer = "Superstore"; List<Person> list = new List<Person>(); list.Add(p); var queryable = list.AsQueryable(); Mock<DbSet<Person>> mockPersonSet = new Mock<DbSet<Person>>(); mockPersonSet.As<IQueryable<Person>>().Setup(set => set.Provider).Returns(queryable.Provider); mockPersonSet.As<IQueryable<Person>>().Setup(set => set.Expression).Returns(queryable.Expression); mockPersonSet.As<IQueryable<Person>>().Setup(set => set.ElementType).Returns(queryable.ElementType); mockPersonSet.As<IQueryable<Person>>().Setup(set => set.GetEnumerator()).Returns(() => queryable.GetEnumerator()); DbSet<Person> dbSet = mockPersonSet.Object as DbSet<Person>; mockPersonSet.Setup(set => set.Local).Returns(dbSet.Local); mockContext.Setup(context => context.Set<Person>()).Returns(mockPersonSet.Object); mockContext.Setup(context => context.Persons).Returns(mockPersonSet.Object)); //Create the repo using the mock data context I created PersonRepository repo = new PersonRepository(mockContext.Object); //Then finally create the controller and perform the test PersonController controller = new PersonController(repo); var result = controller.Create(someEmployerID); //Sometimes throws an SQLException when DbContext.SaveChanges() is called 
+7
c # unit-testing asp.net-mvc moq entity-framework
source share
3 answers

I came up with a less ideal solution for myself, but good enough to allow me to move on. I walked around the DbContext.ChangeTracker.Entries() API by simply adding an abstraction layer to my class that extends a DbContext called MyApplicationDbContext :

 public class MyApplicationDbContext : IdentityDbContext<MyApplicationUser> { //DbSets etc public virtual IEnumerable<MyModelBase> AddedEntries { get { foreach (var entry in ChangeTracker.Entries().Where(entry => entry.State == EntityState.Added)) { MyModelBase model = entry.Entity as MyModelBase; if (model != null) { yield return model; } } } } } 

That way, I can still iterate over the () entries for business logic as described in the problem statement by calling MyApplicationDbContext.AddedEntries instead of MyApplicationDbContext.ChangeTracker.Entries() . However, since I created the virtual property, I can configure the return using Moq:

 List<SomeModel> addedEntries = new List<SomeModel>(); addedEntries.add(someModelWhichWillBeAddedByTheController); mockContext.Setup(context => context.AddedEntries).Returns(addedEntries); 

Thus, the controller will observe someModelWhichWillBeAddedByTheController when the AddedEntries property is AddedEntries . The downside is that I cannot verify the DbContext.ChangeTracker.Entries() code used in real business logic, but I can achieve this later by performing integration tests with the test database.

I could never find the reason that a SQLException thrown on one machine, and not on another.

+2
source share

What you are saying is that it cannot create a connection to your database. This happens when you create your DbContext, where EF will start to do things such as checking if your database needs to be migrated.

It seems to me that you need to mock the constructor of your dbcontext as well. I'm not too familiar with moq, but if I read your code correctly, I don’t see you mocking the constructor.

0
source share

The exception indicates that he cannot create a connection to the database, I suspect that you and your friend have different connection strings in the app.config files, or your friend does not have access to the database.

You are actually writing an integration test, not a unit test. Unit tests are written specifically for the test object. In your code code:

 PersonController controller = new PersonController(); var result = controller.Create(someEmployerID); 

Do not use a real Repository implementation. You need to enter a laughed instance of the repository in PersonController . I assume that you are not using IoC containers , in order to be able to inject it into your controller, you can add another constructor:

 private IRepository _repository; public PersonController() : this(new Repository()) //real implementation {} public PersonController(IRepository repository) { _repository = repository; } // your test var reporitory = new Mock<IRepository>(); var controller = new PersonController(repository.Object); controller.CreateEmployee(someId); // assert that your repository was called repository.Verify(...); 

This method is called injection of poor people , it is not the recommended method of injection, but it is better than specific cases.

Further, if you want to write unit tests (not integration ) for your Repository , then you need to have an interface like IDbContext , which will be the wrapper around your DbContext . And create 2 constructors for your Repository , as in PersonController - less parameter and IDbContext .

UPDATE: Ignore my last statement regarding Repository and DbContext . I checked the documentation of the DbChangeTracker.Entries() method is not virtual, that is, you cannot mock it using the Moq library. You need to use one more mocking infrastructure or test it with intergation tests (without mocking instances).

0
source share

All Articles