Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing EF Save Changes Modifiers. Passing in DbPropertyValues

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.

like image 555
MarkKGreenway Avatar asked Oct 14 '15 07:10

MarkKGreenway


2 Answers

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

like image 69
Todd Sprang Avatar answered Oct 15 '22 13:10

Todd Sprang


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;
    }
}
like image 42
MarkKGreenway Avatar answered Oct 15 '22 13:10

MarkKGreenway