Trying to do some business logic in C# by overriding the EF SaveChanges method.
The idea is to have some advanced calculations on things like if this field has changed update this field. And this field is the sum of the subclass minus some other fields, you know advanced business junk.
Since it's really complicated we want to test the stuffing out of it. Adding tests work great but the updating ones we can't seem to test as we have written an interface where the method in question is passed Signature looks like this
void Update(object entity, DbPropertyValues currentValues, DbPropertyValues originalValues);
When calling it in full EF it works beautifully
public override int SaveChanges()
{
var added = ChangeTracker.Entries().Where(p => p.State == EntityState.Added).Select(p => p.Entity);
var updated = ChangeTracker.Entries().Where(p => p.State == EntityState.Modified).Select(p => p);
var context = new ChangeAndValidationContext();
foreach (var item in added)
{
var strategy = context.SelectStrategy(item);
strategy.Add(item);
}
foreach (var item in updated)
{
var strategy = context.SelectStrategy(item);
strategy.Update(item.Entity, item.CurrentValues, item.OriginalValues);
}
return base.SaveChanges();
}
We just can't figure out how to pass in the DbPropertyValues original or updated for our tests. Please help us figure out how to test that method.
If you have Visual Studio 2012 Update 2+, you can "Add Fakes Assembly" for EntityFramework by right-clicking the project reference in your test project.
Once added, you can create instances of System.Data.Entity.Infrastructure.Fakes.ShimDbPropertyValues
that are completely under your control. e.g.
var shim = new System.Data.Entity.Infrastructure.Fakes.ShimDbPropertyValues();
shim.ItemGetString = s => "Hello, World!";
And when GetString
is called on that fake/shim DbPropertyValues
it will return "Hello, World!".
More details here: https://msdn.microsoft.com/en-us/library/hh549175.aspx
I decided the better way was to change what the strategy expected. Instead of
void Update(object entity, DbPropertyValues currentValues, DbPropertyValues originalValues);
I made it accept
void Update(object entity, Dictionary<string, object> currentValues, Dictionary<string, object> originalValues);
Which meant I changed the values passed to the Update Method
foreach (var item in updated)
{
var strategy = context.SelectStrategy(item);
strategy.Update(item.Entity, item.CurrentValues.ValuesToValuesDictionary(), item.OriginalValues.ValuesToValuesDictionary());
}
Then I created That extension method
public static class DbPropertyValueExtensions
{
public static Dictionary<string, object> ValuesToValuesDictionary(this DbPropertyValues vals)
{
var retVal = new Dictionary<string, object>();
foreach (var propertyName in vals.PropertyNames)
{
if (!retVal.ContainsKey(propertyName))
{
retVal.Add(propertyName, vals[propertyName]);
}
}
return retVal;
}
}
Which meant that my tests needed to pass in those dictionaries.
[Test]
public void DateLastModifiedUpdatesOnUpdate()
{
//Arrange
var toTest = LossFactoryHelper.Create();
var lossCheckAndValidationAddStrategy = new LossChangeAndValidationStrategy();
var now = DateTime.UtcNow;
var originalValues = toTest.GetValuesNow();
//Act
toTest.mny_deductible = -1;
var currentValues = toTest.GetValuesNow();
lossCheckAndValidationAddStrategy.Update(toTest, originalValues, currentValues);
//Assert
Assert.GreaterOrEqual(toTest.clc_DateLastModified, now);
}
And the Extension method to help get a snapshot of the values as not to have to create the dictionary over and over
public static class ReflectionToGetCurrentValuesExtension
{
public static Dictionary<string, object> GetValuesNow(this object obj)
{
var retVal = new Dictionary<string, object>();
var type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.CanRead && property.CanWrite)
{
if (!retVal.ContainsKey(property.Name))
{
retVal.Add(property.Name, property.GetValue(obj));
}
}
}
return retVal;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With