Skip to content

Commit b71f313

Browse files
committed
Added ability to generate unique email addresses for a fixture
1 parent ffe887f commit b71f313

File tree

9 files changed

+171
-38
lines changed

9 files changed

+171
-38
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using NTestDataBuilder.DataSources;
5+
using NTestDataBuilder.DataSources.Geography;
6+
using NTestDataBuilder.EquivalenceClasses.Geo;
7+
using Shouldly;
8+
using Xunit.Extensions;
9+
10+
namespace NTestDataBuilder.Tests.EquivalenceClasses
11+
{
12+
public class GeoEquivalenceClassesTests
13+
{
14+
public static AnonymousValueFixture Any { get; private set; }
15+
16+
public GeoEquivalenceClassesTests()
17+
{
18+
Any = new AnonymousValueFixture();
19+
}
20+
21+
[Theory]
22+
[PropertyData("TestCases")]
23+
public void WhenGettingAnyPersonData_ThenReturnRandomPersonDataWhichIsReasonablyUnique(DataSource<string> source,
24+
List<string> testCases)
25+
{
26+
foreach (var testCase in testCases)
27+
{
28+
testCase.ShouldBeOfType<string>();
29+
testCase.ShouldNotBeNullOrEmpty();
30+
source.Data.ShouldContain(testCase);
31+
}
32+
if (source.Data.Count > 15)
33+
{
34+
var unique = testCases.Distinct().Count();
35+
unique.ShouldBeGreaterThan(5);
36+
}
37+
}
38+
39+
public static IEnumerable<object[]> TestCases
40+
{
41+
get
42+
{
43+
yield return new object[] { new GeoContinentSource(), GenerateTestCasesForSut(Any.Continent) };
44+
yield return new object[] { new GeoCountrySource(), GenerateTestCasesForSut(Any.Country) };
45+
yield return new object[] { new GeoCountryCodeSource(), GenerateTestCasesForSut(Any.CountryCode) };
46+
yield return new object[] { new GeoLatitudeSource(), GenerateTestCasesForSut(Any.Latitude) };
47+
yield return new object[] { new GeoLongitudeSource(), GenerateTestCasesForSut(Any.Longitude) };
48+
}
49+
}
50+
51+
private static List<string> GenerateTestCasesForSut(Func<string> any)
52+
{
53+
var results = new List<string>();
54+
for (int i = 0; i < 10; i++)
55+
{
56+
results.Add(any());
57+
}
58+
return results;
59+
}
60+
}
61+
}

NTestDataBuilder.Tests/EquivalenceClasses/PersonEquivalenceClassesTests.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using NTestDataBuilder.DataSources;
5-
using NTestDataBuilder.DataSources.Geography;
65
using NTestDataBuilder.DataSources.Person;
7-
using NTestDataBuilder.EquivalenceClasses;
8-
using NTestDataBuilder.EquivalenceClasses.Geo;
96
using NTestDataBuilder.EquivalenceClasses.Person;
107
using Shouldly;
8+
using Xunit;
119
using Xunit.Extensions;
1210

1311
namespace NTestDataBuilder.Tests.EquivalenceClasses
@@ -39,6 +37,21 @@ public void WhenGettingAnyPersonData_ThenReturnRandomPersonDataWhichIsReasonably
3937
}
4038
}
4139

40+
[Fact]
41+
public void WhenGettingUniqueEmail_ThenReturnUniqueEmails()
42+
{
43+
var source = new PersonEmailAddressSource();
44+
var generatedValues = new List<string>();
45+
46+
for (var i = 0; i < source.Data.Count; i++)
47+
{
48+
generatedValues.Add(Any.UniqueEmailAddress());
49+
}
50+
51+
generatedValues.Distinct().Count()
52+
.ShouldBe(generatedValues.Count);
53+
}
54+
4255
public static IEnumerable<object[]> TestCases
4356
{
4457
get
@@ -52,12 +65,6 @@ public static IEnumerable<object[]> TestCases
5265
yield return new object[] { new PersonNameFirstMaleSource(), GenerateTestCasesForSut(Any.MaleFirstName) };
5366
yield return new object[] { new PersonNameSuffixSource(), GenerateTestCasesForSut(Any.Suffix) };
5467
yield return new object[] { new PersonNameTitleSource(), GenerateTestCasesForSut(Any.Title) };
55-
56-
yield return new object[] { new GeoContinentSource(), GenerateTestCasesForSut(Any.Continent) };
57-
yield return new object[] { new GeoCountrySource(), GenerateTestCasesForSut(Any.Country) };
58-
yield return new object[] { new GeoCountryCodeSource(), GenerateTestCasesForSut(Any.CountryCode) };
59-
yield return new object[] { new GeoLatitudeSource(), GenerateTestCasesForSut(Any.Latitude) };
60-
yield return new object[] { new GeoLongitudeSource(), GenerateTestCasesForSut(Any.Longitude) };
6168
}
6269
}
6370

NTestDataBuilder.Tests/NTestDataBuilder.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
<Compile Include="DataSources\Generators\RandomGeneratorTests.cs" />
6060
<Compile Include="DataSources\Generators\SequentiaGeneratorTests.cs" />
6161
<Compile Include="DataSources\DataSourceConventionTests.cs" />
62+
<Compile Include="EquivalenceClasses\GeoEquivalenceClassesTests.cs" />
6263
<Compile Include="EquivalenceClasses\PersonEquivalenceClassesTests.cs" />
6364
<Compile Include="EquivalenceClasses\IntegerEquivalenceClassesTests.cs" />
6465
<Compile Include="EquivalenceClasses\EnumEquivalenceClassesTests.cs" />

NTestDataBuilder/AnonymousValueFixture.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public AnonymousValueFixture()
3434
{
3535
LocalValueSuppliers = new List<IAnonymousValueSupplier>();
3636
Fixture = new Fixture();
37+
Bag = new NullingExpandoObject();
3738
RegexGenerator = new RegularExpressionGenerator();
3839
}
3940

@@ -49,6 +50,11 @@ public AnonymousValueFixture()
4950
/// </summary>
5051
public Fixture Fixture { get; private set; }
5152

53+
/// <summary>
54+
/// Dynamic bag of objects that can be used by equivalence classes / anonymous value suppliers to store state.
55+
/// </summary>
56+
public dynamic Bag { get; private set; }
57+
5258
/// <summary>
5359
/// Ordered, immutable collection of default anonymous value suppliers to interrogate when automatically generating an anonymous value.
5460
/// These have the lowest priority and are a fallback if there are no local or global value suppliers that apply.

NTestDataBuilder/DataSources/Person/PersonEmailAddressSource.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using NTestDataBuilder.DataSources.Dictionaries;
2+
using NTestDataBuilder.DataSources.Generators;
23

34
namespace NTestDataBuilder.DataSources.Person
45
{
@@ -7,5 +8,17 @@ namespace NTestDataBuilder.DataSources.Person
78
/// </summary>
89
public class PersonEmailAddressSource : FileDictionarySource
910
{
11+
/// <summary>
12+
/// Create a person email address source with random generation.
13+
/// </summary>
14+
public PersonEmailAddressSource() {}
15+
16+
/// <summary>
17+
/// Create a person email address source with custom generation.
18+
/// </summary>
19+
/// <param name="generator">The generator to use</param>
20+
public PersonEmailAddressSource(IGenerator generator)
21+
: base(generator, new CachedFileDictionaryRepository())
22+
{}
1023
}
1124
}

NTestDataBuilder/EquivalenceClasses/Geo/GeographyEquivalenceClassescs.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ public static class GeographyEquivalenceClassescs
1616
/// <summary>
1717
/// Generate and return a continent name.
1818
/// </summary>
19-
/// <param name="fixture">The fixture to generate a string for</param>
20-
/// <returns>The generated string</returns>
19+
/// <param name="fixture">The fixture to generate a continent for</param>
20+
/// <returns>The generated continent</returns>
2121
public static string Continent(this AnonymousValueFixture fixture)
2222
{
2323
if (_geoContinentSource == null) _geoContinentSource = new GeoContinentSource();
@@ -27,8 +27,8 @@ public static string Continent(this AnonymousValueFixture fixture)
2727
/// <summary>
2828
/// Generate and return a country name.
2929
/// </summary>
30-
/// <param name="fixture">The fixture to generate a string for</param>
31-
/// <returns>The generated string</returns>
30+
/// <param name="fixture">The fixture to generate a country for</param>
31+
/// <returns>The generated country</returns>
3232
public static string Country(this AnonymousValueFixture fixture)
3333
{
3434
if (_geoCountrySource == null) _geoCountrySource = new GeoCountrySource();
@@ -38,8 +38,8 @@ public static string Country(this AnonymousValueFixture fixture)
3838
/// <summary>
3939
/// Generate and return a country code.
4040
/// </summary>
41-
/// <param name="fixture">The fixture to generate a string for</param>
42-
/// <returns>The generated string</returns>
41+
/// <param name="fixture">The fixture to generate a country code for</param>
42+
/// <returns>The generated country code</returns>
4343
public static string CountryCode(this AnonymousValueFixture fixture)
4444
{
4545
if (_geoCountryCodeSource == null) _geoCountryCodeSource = new GeoCountryCodeSource();
@@ -50,8 +50,8 @@ public static string CountryCode(this AnonymousValueFixture fixture)
5050
/// <summary>
5151
/// Generate and return a latitude coordinate.
5252
/// </summary>
53-
/// <param name="fixture">The fixture to generate a string for</param>
54-
/// <returns>The generated string</returns>
53+
/// <param name="fixture">The fixture to generate a latitutde for</param>
54+
/// <returns>The generated latitude</returns>
5555
public static string Latitude(this AnonymousValueFixture fixture)
5656
{
5757
if (_geoLatitudeSource == null) _geoLatitudeSource = new GeoLatitudeSource();
@@ -61,8 +61,8 @@ public static string Latitude(this AnonymousValueFixture fixture)
6161
/// <summary>
6262
/// Generate and return a longitude coordinate.
6363
/// </summary>
64-
/// <param name="fixture">The fixture to generate a string for</param>
65-
/// <returns>The generated string</returns>
64+
/// <param name="fixture">The fixture to generate a longitude for</param>
65+
/// <returns>The generated longitude</returns>
6666
public static string Longitude(this AnonymousValueFixture fixture)
6767
{
6868
if (_geoLongitudeSource == null) _geoLongitudeSource = new GeoLongitudeSource();

NTestDataBuilder/EquivalenceClasses/Person/PersonEquivalenceClasses.cs

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using NTestDataBuilder.DataSources.Person;
1+
using System.Dynamic;
2+
using NTestDataBuilder.DataSources.Generators;
3+
using NTestDataBuilder.DataSources.Person;
24

35
namespace NTestDataBuilder.EquivalenceClasses.Person
46
{
@@ -8,6 +10,7 @@ namespace NTestDataBuilder.EquivalenceClasses.Person
810
public static class NameEquivalenceClasses
911
{
1012
private static PersonEmailAddressSource _personEmailAddressSource;
13+
private static PersonEmailAddressSource _personUniqueEmailAddressSource;
1114
private static PersonLanguageSource _personLanguageSource;
1215
private static PersonNameFirstFemaleSource _personNameFirstFemaleSource;
1316
private static PersonNameFirstSource _personNameFirstSource;
@@ -20,19 +23,35 @@ public static class NameEquivalenceClasses
2023
/// <summary>
2124
/// Generate and return an email address.
2225
/// </summary>
23-
/// <param name="fixture">The fixture to generate a string for</param>
24-
/// <returns>The generated string</returns>
26+
/// <param name="fixture">The fixture to generate a email for</param>
27+
/// <returns>The generated email</returns>
2528
public static string EmailAddress(this AnonymousValueFixture fixture)
2629
{
2730
if (_personEmailAddressSource == null) _personEmailAddressSource = new PersonEmailAddressSource();
2831
return _personEmailAddressSource.Next();
2932
}
3033

34+
/// <summary>
35+
/// Generate and return a unique email address (within the fixture).
36+
/// </summary>
37+
/// <param name="fixture">The fixture to generate a unique email for</param>
38+
/// <returns>The generated unique email</returns>
39+
public static string UniqueEmailAddress(this AnonymousValueFixture fixture)
40+
{
41+
if (fixture.Bag.UniqueEmailAddressSource == null)
42+
{
43+
var generator = new SequentialGenerator(0, _personEmailAddressSource.Data.Count, listShouldBeUnique: true);
44+
fixture.Bag.UniqueEmailAddressSource = new PersonEmailAddressSource(generator);
45+
}
46+
47+
return fixture.Bag.UniqueEmailAddressSource.Next();
48+
}
49+
3150
/// <summary>
3251
/// Generate and return a language name.
3352
/// </summary>
34-
/// <param name="fixture">The fixture to generate a string for</param>
35-
/// <returns>The generated string</returns>
53+
/// <param name="fixture">The fixture to generate a language for</param>
54+
/// <returns>The generated language</returns>
3655
public static string Language(this AnonymousValueFixture fixture)
3756
{
3857
if (_personLanguageSource == null) _personLanguageSource = new PersonLanguageSource();
@@ -42,8 +61,8 @@ public static string Language(this AnonymousValueFixture fixture)
4261
/// <summary>
4362
/// Generate and return a female first name.
4463
/// </summary>
45-
/// <param name="fixture">The fixture to generate a string for</param>
46-
/// <returns>The generated string</returns>
64+
/// <param name="fixture">The fixture to generate a first name for</param>
65+
/// <returns>The generated female first name</returns>
4766
public static string FemaleFirstName(this AnonymousValueFixture fixture)
4867
{
4968
if (_personNameFirstFemaleSource == null) _personNameFirstFemaleSource = new PersonNameFirstFemaleSource();
@@ -53,8 +72,8 @@ public static string FemaleFirstName(this AnonymousValueFixture fixture)
5372
/// <summary>
5473
/// Generate and return a male or female first name.
5574
/// </summary>
56-
/// <param name="fixture">The fixture to generate a string for</param>
57-
/// <returns>The generated string</returns>
75+
/// <param name="fixture">The fixture to generate a first name for</param>
76+
/// <returns>The generated first name</returns>
5877
public static string FirstName(this AnonymousValueFixture fixture)
5978
{
6079
if (_personNameFirstSource == null) _personNameFirstSource = new PersonNameFirstSource();
@@ -64,8 +83,8 @@ public static string FirstName(this AnonymousValueFixture fixture)
6483
/// <summary>
6584
/// Generate and return a male or female full name (first and last names).
6685
/// </summary>
67-
/// <param name="fixture">The fixture to generate a string for</param>
68-
/// <returns>The generated string</returns>
86+
/// <param name="fixture">The fixture to generate a full name for</param>
87+
/// <returns>The generated full name</returns>
6988
public static string FullName(this AnonymousValueFixture fixture)
7089
{
7190
if (_personNameFullSource == null) _personNameFullSource = new PersonNameFullSource();
@@ -75,8 +94,8 @@ public static string FullName(this AnonymousValueFixture fixture)
7594
/// <summary>
7695
/// Generate and return a last name.
7796
/// </summary>
78-
/// <param name="fixture">The fixture to generate a string for</param>
79-
/// <returns>The generated string</returns>
97+
/// <param name="fixture">The fixture to generate a last name for</param>
98+
/// <returns>The generated last name</returns>
8099
public static string LastName(this AnonymousValueFixture fixture)
81100
{
82101
if (_personNameLastSource == null) _personNameLastSource = new PersonNameLastSource();
@@ -86,8 +105,8 @@ public static string LastName(this AnonymousValueFixture fixture)
86105
/// <summary>
87106
/// Generate and return a male first name.
88107
/// </summary>
89-
/// <param name="fixture">The fixture to generate a string for</param>
90-
/// <returns>The generated string</returns>
108+
/// <param name="fixture">The fixture to generate a male first name for</param>
109+
/// <returns>The generated male first name</returns>
91110
public static string MaleFirstName(this AnonymousValueFixture fixture)
92111
{
93112
if (_personNameFirstMaleSource == null) _personNameFirstMaleSource = new PersonNameFirstMaleSource();
@@ -97,8 +116,8 @@ public static string MaleFirstName(this AnonymousValueFixture fixture)
97116
/// <summary>
98117
/// Generate and return name suffix.
99118
/// </summary>
100-
/// <param name="fixture">The fixture to generate a string for</param>
101-
/// <returns>The generated string</returns>
119+
/// <param name="fixture">The fixture to generate a suffix for</param>
120+
/// <returns>The generated suffix</returns>
102121
public static string Suffix(this AnonymousValueFixture fixture)
103122
{
104123
if (_personNameSuffixSource == null) _personNameSuffixSource = new PersonNameSuffixSource();
@@ -108,8 +127,8 @@ public static string Suffix(this AnonymousValueFixture fixture)
108127
/// <summary>
109128
/// Generate and return a name title.
110129
/// </summary>
111-
/// <param name="fixture">The fixture to generate a string for</param>
112-
/// <returns>The generated string</returns>
130+
/// <param name="fixture">The fixture to generate a title for</param>
131+
/// <returns>The generated title</returns>
113132
public static string Title(this AnonymousValueFixture fixture)
114133
{
115134
if (_personNameTitleSource == null) _personNameTitleSource = new PersonNameTitleSource();

NTestDataBuilder/NTestDataBuilder.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
<Compile Include="Lists\ListBuilderExtensions.cs" />
8686
<Compile Include="Lists\ListBuilderGenerator.cs" />
8787
<Compile Include="Lists\ListBuilderInterceptor.cs" />
88+
<Compile Include="NullingExpandoObject.cs" />
8889
<Compile Include="PropertyNameGetter.cs" />
8990
<Compile Include="Suppliers\DefaultEmailValueSupplier.cs" />
9091
<Compile Include="Suppliers\DefaultLastNameValueSupplier.cs" />
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Collections.Generic;
2+
using System.Dynamic;
3+
4+
namespace NTestDataBuilder
5+
{
6+
// http://stackoverflow.com/questions/6291704/net-dynamicobject-implementation-that-returns-null-for-missing-properties-rathe
7+
internal class NullingExpandoObject : DynamicObject
8+
{
9+
private readonly Dictionary<string, object> _values
10+
= new Dictionary<string, object>();
11+
12+
public override bool TryGetMember(GetMemberBinder binder, out object result)
13+
{
14+
// We don't care about the return value...
15+
_values.TryGetValue(binder.Name, out result);
16+
return true;
17+
}
18+
19+
public override bool TrySetMember(SetMemberBinder binder, object value)
20+
{
21+
_values[binder.Name] = value;
22+
return true;
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)