Skip to content

Commit 42d5bb5

Browse files
authored
Struct support (#29)
* Start of support for mapping to structs * Test coverage for struct and interface type evaluation * Support for mapping to nested user-defined, parameterised structs * Extra test coverage, re: issue #10 * Re-using existing target complex type objects instead of using a local variable when target is definitely populated * Support for post-creation callbacks in struct mapping * Tidying * Support for specific-type struct-creation callbacks * Support for object created callbacks for specified source and target struct types * Test coverage for object creation callbacks when mapping from structs to complex types * Organising tests * Extra test coverage for mapping to new root structs * Extending test coverage for mapping to new nested structs / Reusing a solo construction expression instead of creating a composite if possible * Support for runtime-typing constructor arguments / Test coverage for runtime-typed nested struct member matching * Adding MapperData.TargetMemberIsUserStruct helper method * Guarding readonly struct member population with a is non-default comparison * Skipping local variable use when mapping populated, readonly, complex type members * Skipping fallback data source creation for readonly target member * Constructing structs using member initialisations * Simplifying construction of struct mapping member binding expressions * Simplifying target object use when mapping to an enumerable in a write-only context * Ignoring non-simple members when mapping structs - expression compiler will not compile! * Test coverage for complex type struct members * Adding TODO * Support for 'overwriting' a root struct * Test coverage for mapping over struct members with default values * Test coverage for struct overwrite mapping with a null source * Test coverage for overwriting a struct member to the default value * Converting configured constant values at configuration time instead of map time / Test coverage for configuring a constant value to apply to a struct member * Test coverage for mapping over a struct with a source without all matching source members * Test coverage for configuring a custom source value expression for a nested struct member / Fixing 3-argument configured source value bug * Removing unused code * Moving binding - population decision into MemberPopulation * Start of support for struct merging * Extra test coverage for struct merging * Support for merge mapping nested struct members * Erroring if member population callbacks are configured for structs * Support for passing target structs to a configured global pre-mapping callback * Test coverage for configured global post-struct-mapping callback * Skipping module 1 == 0 check on numeric conversions from nullable types * Test coverage for struct constructor parameter data source configuration / Start of support for mapping to enumerables of structs * Test coverage for configuring data sources for struct members * Test coverage for mapping from dictionaries to structs + enumerables of structs * Test coverage for struct enumerable mapping / Fixing overwrite mapping for readonly collections
1 parent a0b7d9b commit 42d5bb5

File tree

84 files changed

+2148
-480
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+2148
-480
lines changed

AgileMapper.UnitTests/AgileMapper.UnitTests.csproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@
9090
<Compile Include="Dictionaries\WhenCreatingRootDictionaryMembers.cs" />
9191
<Compile Include="MapperCloning\WhenCloningMapperObjectFactories.cs" />
9292
<Compile Include="SimpleTypeConversion\WhenConvertingToCharacters.cs" />
93+
<Compile Include="Structs\Configuration\WhenConfiguringStructCreationCallbacks.cs" />
94+
<Compile Include="Structs\Configuration\WhenConfiguringStructDataSources.cs" />
95+
<Compile Include="Structs\Configuration\WhenConfiguringStructMappingCallbacks.cs" />
96+
<Compile Include="Structs\Dictionaries\WhenMappingFromDictionariesToStructs.cs" />
97+
<Compile Include="Structs\WhenMappingOnToStructMembers.cs" />
98+
<Compile Include="Structs\WhenMappingOnToStructs.cs" />
99+
<Compile Include="Structs\WhenMappingOverStructMembers.cs" />
100+
<Compile Include="Structs\WhenMappingOverStructs.cs" />
101+
<Compile Include="Structs\WhenMappingToStructEnumerables.cs" />
102+
<Compile Include="Structs\WhenMappingToUnmappableStructMembers.cs" />
103+
<Compile Include="Structs\WhenMappingToNewStructMembers.cs" />
104+
<Compile Include="Structs\WhenMappingToNewStructs.cs" />
93105
<Compile Include="TestClasses\Earthworm.cs" />
94106
<Compile Include="TestClasses\IPublicInterface.cs" />
95107
<Compile Include="TestClasses\MegaProduct.cs" />
@@ -125,10 +137,13 @@
125137
<Compile Include="TestClasses\OrderUs.cs" />
126138
<Compile Include="TestClasses\PaymentTypeUk.cs" />
127139
<Compile Include="TestClasses\PaymentTypeUs.cs" />
140+
<Compile Include="TestClasses\PublicCtorStruct.cs" />
128141
<Compile Include="TestClasses\PublicFactoryMethod.cs" />
129142
<Compile Include="TestClasses\PublicImplementation.cs" />
130143
<Compile Include="TestClasses\PublicSealed.cs" />
144+
<Compile Include="TestClasses\PublicPropertyStruct.cs" />
131145
<Compile Include="TestClasses\PublicTwoFields.cs" />
146+
<Compile Include="TestClasses\PublicTwoFieldsStruct.cs" />
132147
<Compile Include="TestClasses\PublicTwoParamCtor.cs" />
133148
<Compile Include="TestClasses\Wedding.cs" />
134149
<Compile Include="TestClasses\WeddingDto.cs" />
@@ -214,6 +229,7 @@
214229
<Name>AgileMapper.UnitTests.MoreTestClasses</Name>
215230
</ProjectReference>
216231
</ItemGroup>
232+
<ItemGroup />
217233
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
218234
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
219235
<PropertyGroup>

AgileMapper.UnitTests/Configuration/WhenConfiguringMappingCallbacks.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ public void ShouldExecuteAGlobalPreMappingCallback()
1515
{
1616
var mappedNames = new List<string>();
1717

18-
mapper
19-
.Before
20-
.MappingBegins
18+
mapper.Before.MappingBegins
2119
.Call((s, t) => mappedNames.AddRange(new[] { ((Person)s).Name, ((PersonViewModel)t).Name }));
2220

2321
var source = new Person { Name = "Bernie" };
@@ -36,9 +34,7 @@ public void ShouldExecuteAGlobalPostMappingCallbackConditionally()
3634
{
3735
var mappedNames = new List<string>();
3836

39-
mapper
40-
.After
41-
.MappingEnds
37+
mapper.After.MappingEnds
4238
.If((s, t) => t.GetType() != typeof(Address))
4339
.Call(ctx => mappedNames.AddRange(new[] { ((PersonViewModel)ctx.Source).Name, ((Person)ctx.Target).Name }));
4440

AgileMapper.UnitTests/Configuration/WhenConfiguringObjectCreationCallbacks.cs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,59 +18,62 @@ public void ShouldCallAGlobalObjectCreatedCallback()
1818
{
1919
using (var mapper = Mapper.CreateNew())
2020
{
21-
var createdInstance = default(PublicProperty<int>);
21+
var createdInstance = default(object);
2222

2323
mapper.After
2424
.CreatingInstances
25-
.Call(ctx => createdInstance = (PublicProperty<int>)ctx.CreatedObject);
25+
.Call(ctx => createdInstance = ctx.CreatedObject);
2626

2727
var source = new PublicField<int>();
2828
var result = mapper.Map(source).ToANew<PublicProperty<int>>();
2929

3030
createdInstance.ShouldNotBeNull();
31-
createdInstance.ShouldBe(result);
31+
createdInstance.ShouldBeOfType<PublicProperty<int>>();
32+
createdInstance.ShouldBeSameAs(result);
3233
}
3334
}
3435

3536
[Fact]
3637
public void ShouldWrapAnObjectCreatedCallbackException()
3738
{
38-
Should.Throw<MappingException>(() =>
39+
var createdEx = Should.Throw<MappingException>(() =>
3940
{
4041
using (var mapper = Mapper.CreateNew())
4142
{
4243
mapper.After
4344
.CreatingInstances
44-
.Call(ctx => { throw new InvalidOperationException(); });
45+
.Call(ctx => throw new InvalidOperationException());
4546

4647
mapper.Map(new PublicProperty<int>()).ToANew<PublicField<int>>();
4748
}
4849
});
50+
51+
createdEx.ShouldNotBeNull();
52+
createdEx.Message.ShouldContain("mapping PublicProperty<int> -> PublicField<int>");
4953
}
5054

5155
[Fact]
5256
public void ShouldWrapANestedObjectCreatingCallbackException()
5357
{
54-
var exception = Should.Throw<MappingException>(() =>
58+
var createdEx = Should.Throw<MappingException>(() =>
5559
{
5660
using (var mapper = Mapper.CreateNew())
5761
{
58-
mapper
59-
.After
62+
mapper.After
6063
.CreatingInstancesOf<Address>()
61-
.Call(ctx => { throw new InvalidOperationException("OH NO"); });
64+
.Call(ctx => throw new InvalidOperationException("OH NO"));
6265

6366
mapper.Map(new PersonViewModel { AddressLine1 = "My House" }).ToANew<Person>();
6467
}
6568
});
6669

67-
exception.InnerException.ShouldNotBeNull();
68-
exception.InnerException.ShouldBeOfType<MappingException>();
70+
createdEx.InnerException.ShouldNotBeNull();
71+
createdEx.InnerException.ShouldBeOfType<MappingException>();
6972
// ReSharper disable once PossibleNullReferenceException
70-
exception.InnerException.InnerException.ShouldNotBeNull();
71-
exception.InnerException.InnerException.ShouldBeOfType<InvalidOperationException>();
73+
createdEx.InnerException.InnerException.ShouldNotBeNull();
74+
createdEx.InnerException.InnerException.ShouldBeOfType<InvalidOperationException>();
7275
// ReSharper disable once PossibleNullReferenceException
73-
exception.InnerException.InnerException.Message.ShouldBe("OH NO");
76+
createdEx.InnerException.InnerException.Message.ShouldBe("OH NO");
7477
}
7578

7679
[Fact]

AgileMapper.UnitTests/Configuration/WhenIgnoringMembersIncorrectly.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ public void ShouldErrorIfReadOnlySimpleTypeMemberSpecified()
149149
}
150150
});
151151

152-
configurationEx.Message.ShouldContain("not writeable");
152+
configurationEx.Message.ShouldContain("not mappable");
153153
}
154154

155155
[Fact]

AgileMapper.UnitTests/Members/WhenFindingTargetMembers.cs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -70,41 +70,49 @@ public void ShouldFindAPublicReadOnlyComplexTypeProperty()
7070
}
7171

7272
[Fact]
73-
public void ShouldIgnoreANonPublicField()
73+
public void ShouldFindAPublicReadOnlyArrayField()
7474
{
7575
var member = MemberFinder
76-
.GetTargetMembers(typeof(InternalField<List<byte>>))
76+
.GetTargetMembers(typeof(PublicReadOnlyField<byte[]>))
7777
.FirstOrDefault(m => m.Name == "Value");
7878

79-
member.ShouldBeNull();
79+
member.ShouldNotBeNull();
80+
member.Type.ShouldBe(typeof(byte[]));
81+
member.ElementType.ShouldBe(typeof(byte));
82+
member.IsWriteable.ShouldBeFalse();
8083
}
8184

8285
[Fact]
83-
public void ShouldIgnoreAPublicReadOnlyArrayField()
86+
public void ShouldFindAPublicReadOnlySimpleTypeProperty()
8487
{
8588
var member = MemberFinder
86-
.GetTargetMembers(typeof(PublicReadOnlyField<byte[]>))
89+
.GetTargetMembers(typeof(PublicReadOnlyProperty<long>))
8790
.FirstOrDefault(m => m.Name == "Value");
8891

89-
member.ShouldBeNull();
92+
member.ShouldNotBeNull();
93+
member.Type.ShouldBe(typeof(long));
94+
member.IsWriteable.ShouldBeFalse();
9095
}
9196

9297
[Fact]
93-
public void ShouldIgnoreAPublicReadOnlySimpleTypeProperty()
98+
public void ShouldFindAReadOnlyArrayProperty()
9499
{
95100
var member = MemberFinder
96-
.GetTargetMembers(typeof(PublicReadOnlyProperty<long>))
97-
.FirstOrDefault(m => m.Name == "Value");
101+
.GetTargetMembers(typeof(PublicReadOnlyProperty<long[]>))
102+
.FirstOrDefault(m => m.Name.StartsWith("Value"));
98103

99-
member.ShouldBeNull();
104+
member.ShouldNotBeNull();
105+
member.Type.ShouldBe(typeof(long[]));
106+
member.ElementType.ShouldBe(typeof(long));
107+
member.IsWriteable.ShouldBeFalse();
100108
}
101109

102110
[Fact]
103-
public void ShouldIgnoreAReadOnlyArrayProperty()
111+
public void ShouldIgnoreANonPublicField()
104112
{
105113
var member = MemberFinder
106-
.GetTargetMembers(typeof(PublicReadOnlyProperty<long[]>))
107-
.FirstOrDefault(m => m.Name.StartsWith("Value"));
114+
.GetTargetMembers(typeof(InternalField<List<byte>>))
115+
.FirstOrDefault(m => m.Name == "Value");
108116

109117
member.ShouldBeNull();
110118
}

AgileMapper.UnitTests/Reflection/WhenAccessingTypeInformation.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,23 @@ public void ShouldNotEvaluateAStringAsEnumerable()
4141
#region IsComplex
4242

4343
[Fact]
44-
public void ShouldEvaluateAComplexTypeAsComplex()
44+
public void ShouldEvaluateAClassAsComplex()
4545
{
4646
typeof(Person).IsComplex().ShouldBeTrue();
4747
}
4848

49+
[Fact]
50+
public void ShouldEvaluateAStructAsComplex()
51+
{
52+
typeof(PublicCtorStruct<>).IsComplex().ShouldBeTrue();
53+
}
54+
55+
[Fact]
56+
public void ShouldEvaluateAnInterfaceAsComplex()
57+
{
58+
typeof(IPublicInterface<>).IsComplex().ShouldBeTrue();
59+
}
60+
4961
[Fact]
5062
public void ShouldNotEvaluateAnArrayAsComplex()
5163
{
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
namespace AgileObjects.AgileMapper.UnitTests.Structs.Configuration
2+
{
3+
using Shouldly;
4+
using TestClasses;
5+
using Xunit;
6+
7+
public class WhenConfiguringStructCreationCallbacks
8+
{
9+
[Fact]
10+
public void ShouldCallAGlobalObjectCreatedCallbackWithAStruct()
11+
{
12+
using (var mapper = Mapper.CreateNew())
13+
{
14+
var createdInstance = default(object);
15+
16+
mapper.After
17+
.CreatingInstances
18+
.Call(ctx => createdInstance = ctx.CreatedObject);
19+
20+
var source = new PublicField<long> { Value = 123456 };
21+
var result = mapper.Map(source).ToANew<PublicCtorStruct<int>>();
22+
23+
createdInstance.ShouldNotBeNull();
24+
createdInstance.ShouldBeOfType<PublicCtorStruct<int>>();
25+
result.Value.ShouldBe(123456);
26+
}
27+
}
28+
29+
[Fact]
30+
public void ShouldCallAnObjectCreatedCallbackForASpecifiedStructType()
31+
{
32+
using (var mapper = Mapper.CreateNew())
33+
{
34+
var createdStruct = default(PublicPropertyStruct<int>);
35+
36+
mapper.After
37+
.CreatingInstancesOf<PublicPropertyStruct<int>>()
38+
.Call((s, t, p) => createdStruct = p);
39+
40+
var source = new { Value = "12345" };
41+
var nonMatchingResult = mapper.Map(source).ToANew<PublicField<int>>();
42+
43+
createdStruct.ShouldBeDefault();
44+
nonMatchingResult.Value.ShouldBe(12345);
45+
46+
var matchingResult = mapper.Map(source).ToANew<PublicPropertyStruct<int>>();
47+
48+
createdStruct.ShouldNotBeNull();
49+
createdStruct.ShouldBe(matchingResult);
50+
}
51+
}
52+
53+
[Fact]
54+
public void ShouldCallAnObjectCreatedCallbackForSpecifiedSourceStructType()
55+
{
56+
using (var mapper = Mapper.CreateNew())
57+
{
58+
var creationCount = 0;
59+
60+
mapper.WhenMapping
61+
.From<PublicPropertyStruct<string>>()
62+
.To<Customer>()
63+
.Map(ctx => ctx.Source.Value)
64+
.To(c => c.Name)
65+
.And
66+
.After
67+
.CreatingTargetInstances
68+
.Call(ctx => ++creationCount);
69+
70+
var nonMatchingSource = new { Name = "Goldblum" };
71+
var nonMatchingResult = mapper.Map(nonMatchingSource).ToANew<Customer>();
72+
73+
creationCount.ShouldBe(0);
74+
nonMatchingResult.Name.ShouldBe("Goldblum");
75+
76+
var matchingSource = new PublicPropertyStruct<string> { Value = "Fishy" };
77+
var matchingResult = mapper.Map(matchingSource).ToANew<MysteryCustomer>();
78+
79+
creationCount.ShouldBe(1);
80+
matchingResult.Name.ShouldBe("Fishy");
81+
}
82+
}
83+
84+
[Fact]
85+
public void ShouldCallAnObjectCreatedCallbackForSpecifiedSourceAndTargetStructTypes()
86+
{
87+
using (var mapper = Mapper.CreateNew())
88+
{
89+
var createdStruct = default(PublicCtorStruct<long>);
90+
91+
mapper.WhenMapping
92+
.From<PublicPropertyStruct<int>>()
93+
.To<PublicCtorStruct<long>>()
94+
.After
95+
.CreatingTargetInstances
96+
.Call(ctx => createdStruct = ctx.CreatedObject);
97+
98+
var nonMatchingSource = new { Value = "8765" };
99+
var nonMatchingResult = mapper.Map(nonMatchingSource).ToANew<PublicCtorStruct<long>>();
100+
101+
createdStruct.ShouldBeDefault();
102+
nonMatchingResult.Value.ShouldBe(8765);
103+
104+
var matchingSource = new PublicPropertyStruct<int> { Value = 5678 };
105+
var matchingResult = mapper.Map(matchingSource).ToANew<PublicCtorStruct<long>>();
106+
107+
createdStruct.ShouldNotBeNull();
108+
createdStruct.ShouldBe(matchingResult);
109+
}
110+
}
111+
}
112+
}

0 commit comments

Comments
 (0)