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