// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace ProductivityApiUnitTests
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Internal;
using System.Data.Entity.Resources;
using System.Linq;
using System.Reflection;
using Moq;
using Xunit;
///
/// General unit tests for DbPropertyValues and related classes/methods.
/// Some specific features, such as concurrency, are tested elsewhere.
///
public class DbPropertyValuesTests : TestBase
{
#region Helper classes
private const BindingFlags PropertyBindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
///
/// A type with a variety of different types of properties used for the tests in this class.
///
public class FakeTypeWithProps
{
public FakeTypeWithProps()
{
PublicNonNullStringProp = "NonNullValue";
}
public FakeTypeWithProps(
int id, string publicStringProp, string privateStringProp,
int publicIntProp, int privateIntProp,
int publicIntPropWithPrivateSetter, int publicIntPropWithPrivateGetter,
byte[] publicBinaryProp)
: this()
{
Id = id;
PublicStringProp = publicStringProp;
PrivateStringProp = privateStringProp;
PublicIntProp = publicIntProp;
PrivateIntProp = privateIntProp;
PublicIntPropWithPrivateSetter = publicIntPropWithPrivateSetter;
PublicIntPropWithPrivateGetter = publicIntPropWithPrivateGetter;
PublicBinaryProp = publicBinaryProp;
}
public virtual int Id { get; set; }
public virtual string PublicStringProp { get; set; }
public virtual string PublicNonNullStringProp { get; set; }
private string PrivateStringProp { get; set; }
public virtual string PublicReadonlyStringProp
{
get { return "PublicReadonlyStringProp"; }
}
private string PrivateReadonlyStringProp
{
get { return "PrivateReadonlyStringProp"; }
}
public virtual string PublicWriteOnlyStringProp
{
set { }
}
private string PrivateWriteOnlyStringProp
{
set { }
}
public virtual int PublicIntProp { get; set; }
private int PrivateIntProp { get; set; }
public virtual int PublicIntPropWithPrivateSetter { get; private set; }
public virtual int PublicIntPropWithPrivateGetter { private get; set; }
public virtual byte[] PublicBinaryProp { get; set; }
public virtual FakeTypeWithProps NestedObject { get; set; }
}
public class FakeDerivedTypeWithProps : FakeTypeWithProps
{
}
#endregion
#region Tests for copying values to an object
[Fact]
public void Non_Generic_DbPropertyValues_uses_ToObject_on_InternalPropertyValues()
{
var properties = new Dictionary
{
{ "Id", 1 }
};
var values = new DbPropertyValues(new TestInternalPropertyValues(properties));
var clone = (FakeTypeWithProps)values.ToObject();
Assert.Equal(1, clone.Id);
}
[Fact]
public void ToObject_for_entity_uses_CreateObject_to_return_instance_of_correct_type()
{
var values = new TestInternalPropertyValues(null, isEntityValues: true);
values.MockInternalContext.Setup(c => c.CreateObject(typeof(FakeTypeWithProps))).Returns(new FakeDerivedTypeWithProps());
var clone = values.ToObject();
Assert.IsType(clone);
}
[Fact]
public void ToObject_for_complex_object_does_not_use_CreateObject_to_create_instance()
{
var values = new TestInternalPropertyValues();
var clone = values.ToObject();
values.MockInternalContext.Verify(c => c.CreateObject(typeof(FakeTypeWithProps)), Times.Never());
Assert.IsType(clone);
}
[Fact]
public void ToObject_sets_all_properties_from_the_dictionary_onto_the_object_including_those_with_private_setters()
{
var properties = new Dictionary
{
{ "Id", 1 },
{ "PublicStringProp", "PublicStringPropValue" },
{ "PrivateStringProp", "PrivateStringPropValue" },
{ "PublicIntProp", 2 },
{ "PrivateIntProp", 3 },
{ "PublicIntPropWithPrivateSetter", 4 },
{ "PublicIntPropWithPrivateGetter", 5 },
{ "PublicBinaryProp", new byte[] { 3, 1, 4, 1, 5, 9 } },
};
var values = new TestInternalPropertyValues(properties);
var clone = (FakeTypeWithProps)values.ToObject();
Assert.Equal(1, clone.Id);
Assert.Equal("PublicStringPropValue", clone.PublicStringProp);
Assert.Equal(2, clone.PublicIntProp);
Assert.Equal(
"PrivateStringPropValue",
typeof(FakeTypeWithProps).GetProperty("PrivateStringProp", PropertyBindingFlags).GetValue(clone, null));
Assert.Equal(3, typeof(FakeTypeWithProps).GetProperty("PrivateIntProp", PropertyBindingFlags).GetValue(clone, null));
Assert.Equal(4, clone.PublicIntPropWithPrivateSetter);
Assert.Equal(
5, typeof(FakeTypeWithProps).GetProperty("PublicIntPropWithPrivateGetter", PropertyBindingFlags).GetValue(clone, null));
Assert.True(DbHelpers.KeyValuesEqual(new byte[] { 3, 1, 4, 1, 5, 9 }, clone.PublicBinaryProp));
}
[Fact]
public void ToObject_ignores_properties_that_are_conceptually_in_shadow_state()
{
var properties = new Dictionary
{
{ "Id", 1 },
{ "MissingProp", "MissingPropValue" }
};
var values = new TestInternalPropertyValues(properties);
var clone = (FakeTypeWithProps)values.ToObject();
Assert.Equal(1, clone.Id);
}
[Fact]
public void ToObject_ignores_properties_that_are_readonly()
{
var properties = new Dictionary
{
{ "Id", 1 },
{ "PublicReadonlyStringProp", "Foo" },
{ "PrivateReadonlyStringProp", "Foo" }
};
var values = new TestInternalPropertyValues(properties);
var clone = (FakeTypeWithProps)values.ToObject();
Assert.Equal(1, clone.Id);
Assert.Equal("PublicReadonlyStringProp", clone.PublicReadonlyStringProp);
Assert.Equal(
"PrivateReadonlyStringProp",
typeof(FakeTypeWithProps).GetProperty("PrivateReadonlyStringProp", PropertyBindingFlags).GetValue(clone, null));
}
[Fact]
public void ToObject_can_set_reference_propeties_to_null()
{
var properties = new Dictionary
{
{ "PublicNonNullStringProp", null }
};
var values = new TestInternalPropertyValues(properties);
var clone = (FakeTypeWithProps)values.ToObject();
Assert.Null(clone.PublicNonNullStringProp);
}
[Fact]
public void ToObject_returns_nested_property_dictionary_as_cloned_object()
{
var nestedProperties = new Dictionary
{
{ "Id", 1 }
};
var nestedValues = new TestInternalPropertyValues(nestedProperties);
var properties = new Dictionary
{
{ "NestedObject", nestedValues }
};
var values = new TestInternalPropertyValues(properties, new[] { "NestedObject" });
var clone = (FakeTypeWithProps)values.ToObject();
Assert.Equal(1, clone.NestedObject.Id);
}
[Fact]
public void ToObject_ignores_null_nested_property_dictionary()
{
var properties = new Dictionary
{
{ "Id", 1 },
{ "NestedObject", null }
};
var values = new TestInternalPropertyValues(properties, new[] { "NestedObject" });
var clone = (FakeTypeWithProps)values.ToObject();
Assert.Equal(1, clone.Id);
Assert.Null(clone.NestedObject);
}
[Fact]
public void ToObject_throws_when_trying_to_set_null_values_onto_non_nullable_properties()
{
var values = new TestInternalPropertyValues(
new Dictionary
{
{ "Id", 1 }
});
values["Id"] = null;
Assert.Equal(
Strings.DbPropertyValues_CannotSetNullValue("Id", "Int32", "FakeTypeWithProps"),
Assert.Throws(() => values.ToObject()).Message);
}
#endregion
#region Tests for setting values from an object
[Fact]
public void Attempt_to_copy_values_from_null_object_throws()
{
var values = new DbPropertyValues(new TestInternalPropertyValues());
Assert.Equal("obj", Assert.Throws(() => values.SetValues((object)null)).ParamName);
}
[Fact]
public void SetValues_copies_values_from_object_to_property_dictionary()
{
var properties = new Dictionary
{
{ "Id", 0 },
{ "PublicStringProp", null },
{ "PrivateStringProp", null },
{ "PublicIntProp", 0 },
{ "PrivateIntProp", 0 },
{ "PublicIntPropWithPrivateSetter", 0 },
{ "PublicIntPropWithPrivateGetter", 0 },
{ "PublicBinaryProp", null },
};
var values = new TestInternalPropertyValues(properties);
var obj = new FakeTypeWithProps(
1, "PublicStringPropValue", "PrivateStringPropValue", 2, 3, 4, 5, new byte[] { 3, 1, 4, 1, 5, 9 });
values.SetValues(obj);
Assert.Equal(1, values["Id"]);
Assert.Equal("PublicStringPropValue", values["PublicStringProp"]);
Assert.Equal(2, values["PublicIntProp"]);
Assert.Equal("PrivateStringPropValue", values["PrivateStringProp"]);
Assert.Equal(3, values["PrivateIntProp"]);
Assert.Equal(4, values["PublicIntPropWithPrivateSetter"]);
Assert.Equal(5, values["PublicIntPropWithPrivateGetter"]);
Assert.True(DbHelpers.KeyValuesEqual(new byte[] { 3, 1, 4, 1, 5, 9 }, values["PublicBinaryProp"]));
}
[Fact]
public void Attempt_to_copy_values_from_object_of_differnt_type_copies_no_properties_if_no_properties_match()
{
var properties = new Dictionary
{
{ "PublicStringProp", null },
{ "PrivateStringProp", null },
{ "PublicIntProp", 0 },
{ "PrivateIntProp", 0 },
};
var values = new TestInternalPropertyValues(properties);
values.SetValues("Bang!");
Assert.Null(values["PublicStringProp"]);
Assert.Equal(0, values["PublicIntProp"]);
Assert.Null(values["PrivateStringProp"]);
Assert.Equal(0, values["PrivateIntProp"]);
}
public class FakeTypeWithSomeProps
{
public FakeTypeWithSomeProps(string publicStringProp, int privateIntProp, int someOtherIntProp)
{
PublicStringProp = publicStringProp;
PrivateIntProp = privateIntProp;
SomeOtherIntProp = someOtherIntProp;
}
public string PublicStringProp { get; set; }
private int PrivateIntProp { get; set; }
public int SomeOtherIntProp { get; set; }
}
[Fact]
public void Attempt_to_copy_values_from_object_of_differnt_type_copies_only_properties_that_match()
{
Attempt_to_copy_values_from_object_of_differnt_type_copies_only_properties_that_match_implementation(
new FakeTypeWithSomeProps("PublicStringPropValue", 3, 4));
}
[Fact]
public void Attempt_to_copy_values_from_anonymous_object_copies_only_properties_that_match()
{
var obj = new
{
PublicStringProp = "PublicStringPropValue",
PrivateIntProp = 3,
SomeDifferentIntProp = 4
};
Attempt_to_copy_values_from_object_of_differnt_type_copies_only_properties_that_match_implementation(obj);
}
private void Attempt_to_copy_values_from_object_of_differnt_type_copies_only_properties_that_match_implementation(object obj)
{
var properties = new Dictionary
{
{ "Id", 0 },
{ "PublicStringProp", null },
{ "PrivateStringProp", null },
{ "PublicIntProp", 0 },
{ "PrivateIntProp", 0 },
{ "PublicIntPropWithPrivateSetter", 0 },
{ "PublicIntPropWithPrivateGetter", 0 },
{ "PublicBinaryProp", null },
};
var values = new TestInternalPropertyValues(properties);
values.SetValues(obj);
Assert.Equal(0, values["Id"]);
Assert.Equal("PublicStringPropValue", values["PublicStringProp"]);
Assert.Equal(0, values["PublicIntProp"]);
Assert.Null(values["PrivateStringProp"]);
Assert.Equal(3, values["PrivateIntProp"]);
Assert.Equal(0, values["PublicIntPropWithPrivateSetter"]);
Assert.Equal(0, values["PublicIntPropWithPrivateGetter"]);
Assert.Null(values["PublicBinaryProp"]);
}
[Fact]
public void Non_Generic_DbPropertyValues_SetValues_works_on_the_underlying_dictionary()
{
var properties = new Dictionary
{
{ "Id", 0 }
};
var values = new DbPropertyValues(new TestInternalPropertyValues(properties));
values.SetValues(
new FakeTypeWithProps
{
Id = 1
});
Assert.Equal(1, values["Id"]);
}
[Fact]
public void Calling_SetValues_with_instance_of_derived_type_works()
{
var properties = new Dictionary
{
{ "Id", 0 }
};
var values = new DbPropertyValues(new TestInternalPropertyValues(properties));
values.SetValues(
new FakeDerivedTypeWithProps
{
Id = 1
});
Assert.Equal(1, values["Id"]);
}
[Fact]
public void SetValues_ignores_properties_that_are_conceptually_in_shadow_state()
{
var properties = new Dictionary
{
{ "Id", 0 },
{ "MissingProp", "MissingPropValue" }
};
var values = new TestInternalPropertyValues(properties);
values.SetValues(
new FakeTypeWithProps
{
Id = 1
});
Assert.Equal(1, values["Id"]);
Assert.Equal("MissingPropValue", values["MissingProp"]);
}
[Fact]
public void SetValues_ignores_properties_that_are_write_only()
{
var properties = new Dictionary
{
{ "Id", 0 },
{ "PublicWriteOnlyStringProp", "Foo" },
{ "PrivateWriteOnlyStringProp", "Bar" }
};
var values = new TestInternalPropertyValues(properties);
values.SetValues(
new FakeTypeWithProps
{
Id = 1
});
Assert.Equal(1, values["Id"]);
Assert.Equal("Foo", values["PublicWriteOnlyStringProp"]);
Assert.Equal("Bar", values["PrivateWriteOnlyStringProp"]);
}
[Fact]
public void SetValues_can_set_reference_propeties_to_null()
{
var properties = new Dictionary
{
{ "Id", 0 },
{ "PublicStringProp", "NonNull" }
};
var values = new TestInternalPropertyValues(properties);
values.SetValues(
new FakeTypeWithProps
{
Id = 1
});
Assert.Equal(1, values["Id"]);
Assert.Null(values["PublicStringProp"]);
}
[Fact]
public void SetValues_sets_values_from_complex_object_into_nested_property_dictionary()
{
var nestedProperties = new Dictionary
{
{ "Id", 0 }
};
var nestedValues = new TestInternalPropertyValues(nestedProperties);
var properties = new Dictionary
{
{ "Id", 0 },
{ "NestedObject", nestedValues }
};
var values = new TestInternalPropertyValues(properties, new[] { "NestedObject" });
values.SetValues(
new FakeTypeWithProps
{
Id = 1,
NestedObject = new FakeTypeWithProps
{
Id = 2
}
});
Assert.Equal(1, values["Id"]);
Assert.Equal(2, nestedValues["Id"]);
}
[Fact]
public void SetValues_when_complex_object_is_null_throws()
{
var nestedProperties = new Dictionary
{
{ "Id", 0 }
};
var nestedValues = new TestInternalPropertyValues(nestedProperties);
var properties = new Dictionary
{
{ "Id", 0 },
{ "NestedObject", nestedValues }
};
var values = new TestInternalPropertyValues(properties, new[] { "NestedObject" });
var obj = new FakeTypeWithProps
{
Id = 1,
NestedObject = null
};
Assert.Equal(
Strings.DbPropertyValues_ComplexObjectCannotBeNull("NestedObject", typeof(FakeTypeWithProps).Name),
Assert.Throws(() => values.SetValues(obj)).Message);
}
[Fact]
public void SetValues_when_nested_property_dictionary_in_source_is_null_throws()
{
var properties = new Dictionary
{
{ "Id", 0 },
{ "NestedObject", null }
};
var values = new TestInternalPropertyValues(properties, new[] { "NestedObject" });
var obj = new FakeTypeWithProps
{
Id = 1,
NestedObject = new FakeTypeWithProps
{
Id = 2
}
};
Assert.Equal(
Strings.DbPropertyValues_NestedPropertyValuesNull("NestedObject", typeof(FakeTypeWithProps).Name),
Assert.Throws(() => values.SetValues(obj)).Message);
}
[Fact]
public void SetValues_does_not_attempt_to_set_values_that_are_not_different()
{
var properties = new Dictionary
{
{ "PublicStringProp", "PublicStringPropValue" },
{ "PublicIntProp", 2 },
{ "PublicBinaryProp", new byte[] { 3, 1, 4, 1, 5, 9 } },
};
var values = new TestInternalPropertyValues(properties);
var obj = new FakeTypeWithProps
{
PublicStringProp = "PublicStringPropValue",
PublicIntProp = 2,
PublicBinaryProp = new byte[] { 3, 1, 4, 1, 5, 9 }
};
values.SetValues(obj);
values.GetMockItem("PublicStringProp").VerifySet(i => i.Value = It.IsAny