From 65f644c751bf5a3714b1aa71e39a62f71685af88 Mon Sep 17 00:00:00 2001 From: Davyd McColl Date: Tue, 17 Dec 2024 11:41:18 +0200 Subject: [PATCH] :bug: CopyPropertiesTo should copy values from non-nullable to nullable with same underlying type --- .../TestGetRandom.cs | 4 - .../TestObjectExtensions.cs | 3 +- .../TestDateTimeExtensions.cs | 3 + .../TestObjectExtensions.cs | 81 +++++++++++++++++++ .../PeanutButter.Utils/ObjectExtensions.cs | 26 ++++++ 5 files changed, 111 insertions(+), 6 deletions(-) diff --git a/source/TestUtils/PeanutButter.RandomGenerators.Core.Tests/TestGetRandom.cs b/source/TestUtils/PeanutButter.RandomGenerators.Core.Tests/TestGetRandom.cs index f778d84f56..cb1525912e 100644 --- a/source/TestUtils/PeanutButter.RandomGenerators.Core.Tests/TestGetRandom.cs +++ b/source/TestUtils/PeanutButter.RandomGenerators.Core.Tests/TestGetRandom.cs @@ -1,9 +1,5 @@ using System.Collections.Generic; -using NUnit.Framework; -using static PeanutButter.RandomGenerators.RandomValueGen; -using NExpect; using PeanutButter.RandomGenerators.Core.Tests.Domain; -using static NExpect.Expectations; namespace PeanutButter.RandomGenerators.Core.Tests { diff --git a/source/TestUtils/PeanutButter.RandomGenerators.Core.Tests/TestObjectExtensions.cs b/source/TestUtils/PeanutButter.RandomGenerators.Core.Tests/TestObjectExtensions.cs index 3fa5434dc7..4cdc1a6b36 100644 --- a/source/TestUtils/PeanutButter.RandomGenerators.Core.Tests/TestObjectExtensions.cs +++ b/source/TestUtils/PeanutButter.RandomGenerators.Core.Tests/TestObjectExtensions.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; using NSubstitute; -using NUnit.Framework; namespace PeanutButter.RandomGenerators.Core.Tests; [TestFixture] -public partial class TestObjectExtensions +public class TestObjectExtensions { [TestFixture] public class Randomized diff --git a/source/Utils/PeanutButter.Utils.NetCore.Tests/TestDateTimeExtensions.cs b/source/Utils/PeanutButter.Utils.NetCore.Tests/TestDateTimeExtensions.cs index c555b6cf9b..7fe4a579fd 100644 --- a/source/Utils/PeanutButter.Utils.NetCore.Tests/TestDateTimeExtensions.cs +++ b/source/Utils/PeanutButter.Utils.NetCore.Tests/TestDateTimeExtensions.cs @@ -731,7 +731,10 @@ private static string ZeroPad(int value, int places = 2) { var result = value.ToString(); while (result.Length < places) + { result = "0" + result; + } + return result; } diff --git a/source/Utils/PeanutButter.Utils.NetCore.Tests/TestObjectExtensions.cs b/source/Utils/PeanutButter.Utils.NetCore.Tests/TestObjectExtensions.cs index 757cfd2e85..b58035d709 100644 --- a/source/Utils/PeanutButter.Utils.NetCore.Tests/TestObjectExtensions.cs +++ b/source/Utils/PeanutButter.Utils.NetCore.Tests/TestObjectExtensions.cs @@ -1114,6 +1114,7 @@ public class CopyingPropertiesFromOneObjectToAnother [Test] public void GivenDestWithSameProperty_CopiesValue() { + TestCopyFor(); TestCopyFor(); TestCopyFor(); TestCopyFor(); @@ -1145,7 +1146,67 @@ private static void TestCopyFor() var src = new Simple(); var dst = new Simple(); while (dst.prop.Equals(src.prop)) + { + dst.Randomize(); + } + + src.CopyPropertiesTo(dst); + + //---------------Test Result ----------------------- + Expect(dst.prop) + .To.Equal(src.prop); + } + + [Test] + public void ShouldCopyNullableWithValueToNonNullable() + { + // Arrange + // Act + TestNullableCopyFor(); + TestNullableCopyFor(); + TestNullableCopyFor(); + TestNullableCopyFor(); + TestNullableCopyFor(); + TestNullableCopyFor(); + TestNullableCopyFor(); + TestNullableCopyFor(); + TestNullableCopyFor(); + TestNullableCopyFor(); + // Assert + } + + public class Foo1 + { + public Numbers Numbers { get; set; } + } + + public class Foo2 + { + public Numbers? Numbers { get; set; } + } + + public enum Numbers + { + One, + Two, + Three + } + + private static void TestNullableCopyFor() + where T : struct + { + //---------------Set up test pack------------------- + + //---------------Assert Precondition---------------- + + //---------------Execute Test ---------------------- + var src = GetRandom>(); + var dst = new NullableSimple(); + while (dst.prop.Equals(src.prop)) + { dst.Randomize(); + } + src.CopyPropertiesTo(dst); //---------------Test Result ----------------------- @@ -1161,7 +1222,9 @@ public void ComplexTypesAreTraversedButOnlySimplePropertiesAreCopied() var src = new Complex(); var dst = new Complex(); while (dst.prop.prop.Equals(src.prop.prop)) + { dst.prop.Randomize(); + } //---------------Assert Precondition---------------- Expect(dst) @@ -1187,7 +1250,9 @@ public void WhenDeepIsFalse_ComplexTypesAreTraversedAndRefCopied() var src = new Complex(); var dst = new Complex(); while (dst.prop.prop.Equals(src.prop.prop)) + { dst.prop.Randomize(); + } //---------------Assert Precondition---------------- Expect(dst) @@ -3687,6 +3752,22 @@ public void Randomize() } } + public class NullableSimple + where T : struct + { + public T? prop { get; set; } + + public NullableSimple() + { + Randomize(); + } + + public void Randomize() + { + prop = GetRandom(); + } + } + public class Complex { public Simple prop { get; set; } diff --git a/source/Utils/PeanutButter.Utils/ObjectExtensions.cs b/source/Utils/PeanutButter.Utils/ObjectExtensions.cs index b7c7e5873c..630b6f62a1 100644 --- a/source/Utils/PeanutButter.Utils/ObjectExtensions.cs +++ b/source/Utils/PeanutButter.Utils/ObjectExtensions.cs @@ -430,6 +430,30 @@ public static void CopyPropertiesTo(this object src, object dst, bool deep, para pi => Tuple.Create(pi.Name, pi.PropertyType), pi => pi ); + foreach (var item in targetPropertyCache.ToArray()) + { + if (!item.Value.PropertyType.IsNullableType()) + { + continue; + } + + var underlyingType = Nullable.GetUnderlyingType( + item.Value.PropertyType + ); + if (underlyingType is null) + { + continue; + } + + var key = Tuple.Create( + item.Key.Item1, + underlyingType + ); + targetPropertyCache.TryAdd( + key, + item.Value + ); + } foreach (var srcPropInfo in srcPropInfos.Where( pi => pi.CanRead && @@ -457,6 +481,8 @@ out var matchingTarget } } + private static readonly Type NullableType = typeof(Nullable<>); + private static readonly ConcurrentDictionary PropertyCache = new(); private static readonly Func[]