Skip to content

Commit c6d448d

Browse files
committed
Finish wiring up multiple return values using "BlockData" objects
1 parent c0453ff commit c6d448d

File tree

6 files changed

+226
-18
lines changed

6 files changed

+226
-18
lines changed

IntelliTect.TestTools.TestFramework.Tests/TestCaseTests/MultipleDependencyTests.cs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
using IntelliTect.TestTools.TestFramework.Tests.TestData.Dependencies;
2-
using IntelliTect.TestTools.TestFramework.Tests.TestData.TestBlocks;
1+
using IntelliTect.TestTools.TestFramework.Tests.TestData.TestBlocks;
2+
using System;
3+
using System.Threading.Tasks;
34
using Xunit;
45

56
namespace IntelliTect.TestTools.TestFramework.Tests.TestCaseTests
67
{
7-
public class MultipleDependencyTests
8+
public class MultipleDependencyTests : TestBase
89
{
910
[Fact]
1011
public void ReturnDuplicateTypesDoesNotThrow()
@@ -38,5 +39,42 @@ public void FetchByObjectInstanceForMultipleDependencies()
3839
// Assert
3940
Assert.True(tc.Passed);
4041
}
42+
43+
[Fact]
44+
public async Task ReturnMultipleObjectsStoresEachObjectSeparately()
45+
{
46+
// Arrange
47+
TestCase tc = new TestBuilder()
48+
.AddTestBlock<ExampleBlockWithMultipleReturns>(true)
49+
.AddTestBlock<ExampleTestBlockWithExecuteArg>()
50+
.AddTestBlock<ExampleTestBlockWithBoolReturn>()
51+
.Build();
52+
53+
// Act
54+
await tc.ExecuteAsync();
55+
56+
// Assert
57+
Assert.True(tc.Passed);
58+
}
59+
60+
[Fact]
61+
public async Task ReturnMultipleObjectsExecutesSubsequentTestBlocks()
62+
{
63+
// Arrange
64+
65+
TestCase tc = new TestBuilder()
66+
.AddTestBlock<ExampleBlockWithMultipleReturns>(false)
67+
.AddTestBlock<ExampleTestBlockWithExecuteArg>()
68+
.AddTestBlock<ExampleTestBlockWithBoolReturn>()
69+
.Build();
70+
71+
// Act
72+
var result = await Assert.ThrowsAsync<TestCaseException>(tc.ExecuteAsync);
73+
74+
// Assert
75+
Assert.False(tc.Passed);
76+
Assert.NotNull(result.InnerException);
77+
Assert.True(result.InnerException.GetType() == typeof(DivideByZeroException));
78+
}
4179
}
4280
}

IntelliTect.TestTools.TestFramework.Tests/TestData/TestBlocks/MultipleDependencyBlocks.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,12 @@ public void Execute(string inputText, int inputNumber)
2121
Assert.Equal(1234, inputNumber);
2222
}
2323
}
24+
25+
public class ExampleBlockWithMultipleReturns : TestBlock
26+
{
27+
public BlockData<string, bool> Execute(bool returnValue)
28+
{
29+
return new BlockData<string, bool>("Testing", returnValue);
30+
}
31+
}
2432
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using Xunit;
3+
4+
namespace IntelliTect.TestTools.TestFramework.Tests;
5+
6+
public class TestDataTests
7+
{
8+
[Fact]
9+
public void BlockDataT1T2ThrowsExceptionOnDuplicateTypes()
10+
{
11+
var exception = Assert.Throws<TypeInitializationException>(() =>
12+
{
13+
var _ = new BlockData<int, int>(1, 2);
14+
});
15+
Assert.NotNull(exception.InnerException);
16+
Assert.Equal(typeof(InvalidOperationException), exception.InnerException.GetType());
17+
Assert.Equal("Duplicate type found: Int32 appears multiple times. BlockData must use different types to avoid unexpected behavior by the TestCase DI Container.",
18+
exception.InnerException.Message);
19+
}
20+
21+
[Fact]
22+
public void BlockDataT1T2T3ThrowsExceptionOnDuplicateTypes()
23+
{
24+
var exception = Assert.Throws<TypeInitializationException>(() =>
25+
{
26+
var _ = new BlockData<int, bool, int>(1, true, 2);
27+
});
28+
Assert.NotNull(exception.InnerException);
29+
Assert.Equal(typeof(InvalidOperationException), exception.InnerException.GetType());
30+
Assert.Equal("Duplicate type found: Int32 appears multiple times. BlockData must use different types to avoid unexpected behavior by the TestCase DI Container.",
31+
exception.InnerException.Message);
32+
}
33+
34+
[Fact]
35+
public void BlockDataT1T2T3T4ThrowsExceptionOnDuplicateTypes()
36+
{
37+
var exception = Assert.Throws<TypeInitializationException>(() =>
38+
{
39+
var _ = new BlockData<bool, double, int, int>(false, 0.5, 1, 2);
40+
});
41+
Assert.NotNull(exception.InnerException);
42+
Assert.Equal(typeof(InvalidOperationException), exception.InnerException.GetType());
43+
Assert.Equal("Duplicate type found: Int32 appears multiple times. BlockData must use different types to avoid unexpected behavior by the TestCase DI Container.",
44+
exception.InnerException.Message);
45+
}
46+
47+
[Fact]
48+
public void BlockDataT1T2DoesNotThrowExceptionOnWithUniqueTypes()
49+
{
50+
BlockData<int, bool> blockData = new(1, true);
51+
Assert.Equal(2, blockData.Data.Count);
52+
}
53+
54+
[Fact]
55+
public void BlockDataT1T2T3DoesNotThrowExceptionOnWithUniqueTypes()
56+
{
57+
BlockData<ArgumentException, Exception, bool> blockData = new(new ArgumentException(), new Exception(), true);
58+
Assert.Equal(3, blockData.Data.Count);
59+
}
60+
61+
[Fact]
62+
public void BlockDataT1T2T3T4DoesNotThrowExceptionOnWithUniqueTypes()
63+
{
64+
BlockData<int, bool, string, double> blockData = new(1, true, "", 0.5);
65+
Assert.Equal(4, blockData.Data.Count);
66+
}
67+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace IntelliTect.TestTools.TestFramework;
5+
6+
public interface IBlockData
7+
{
8+
public List<KeyValuePair<Type, object?>> Data { get; }
9+
}
10+
11+
public class BlockData<T1, T2>(T1 data1, T2 data2) : IBlockData
12+
{
13+
static BlockData()
14+
{
15+
ValidateData.ValidateUniqueTypes(typeof(T1), typeof(T2));
16+
}
17+
18+
public List<KeyValuePair<Type, object?>> Data { get; } =
19+
[
20+
new KeyValuePair<Type, object?>(typeof(T1), data1),
21+
new KeyValuePair<Type, object?>(typeof(T2), data2)
22+
];
23+
}
24+
25+
public class BlockData<T1, T2, T3>(T1 data1, T2 data2, T3 data3) : IBlockData
26+
{
27+
static BlockData()
28+
{
29+
ValidateData.ValidateUniqueTypes(typeof(T1), typeof(T2), typeof(T3));
30+
}
31+
32+
public List<KeyValuePair<Type, object?>> Data { get; } =
33+
[
34+
new KeyValuePair<Type, object?>(typeof(T1), data1),
35+
new KeyValuePair<Type, object?>(typeof(T2), data2),
36+
new KeyValuePair<Type, object?>(typeof(T3), data3)
37+
];
38+
}
39+
40+
public class BlockData<T1, T2, T3, T4>(T1 data1, T2 data2, T3 data3, T4 data4) : IBlockData
41+
{
42+
static BlockData()
43+
{
44+
ValidateData.ValidateUniqueTypes(typeof(T1), typeof(T2), typeof(T3), typeof(T4));
45+
}
46+
47+
public List<KeyValuePair<Type, object?>> Data { get; } =
48+
[
49+
new KeyValuePair<Type, object?>(typeof(T1), data1),
50+
new KeyValuePair<Type, object?>(typeof(T2), data2),
51+
new KeyValuePair<Type, object?>(typeof(T3), data3),
52+
new KeyValuePair<Type, object?>(typeof(T4), data4)
53+
];
54+
}
55+
56+
internal static class ValidateData
57+
{
58+
internal static void ValidateUniqueTypes(params Type[] types)
59+
{
60+
HashSet<Type> seenTypes = [];
61+
foreach(Type type in types)
62+
{
63+
if (!seenTypes.Add(type))
64+
{
65+
throw new InvalidOperationException($"Duplicate type found: {type.Name} appears multiple times. BlockData must use different types to avoid unexpected behavior by the TestCase DI Container.");
66+
}
67+
}
68+
}
69+
}

IntelliTect.TestTools.TestFramework/TestBuilder.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,18 @@ private void GatherDependencies(
354354

355355
if (executeReturns != typeof(void))
356356
{
357-
outputs.Add(executeReturns);
357+
if (executeReturns.GetInterfaces().Any(x => x == typeof(IBlockData)))
358+
{
359+
Type[] blockData = executeReturns.GenericTypeArguments;
360+
foreach (Type bd in blockData)
361+
{
362+
outputs.Add(bd);
363+
}
364+
}
365+
else
366+
{
367+
outputs.Add(executeReturns);
368+
}
358369
}
359370
}
360371

IntelliTect.TestTools.TestFramework/TestCase.cs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,15 @@ public async Task ExecuteAsync()
141141
{
142142
throw new TestCaseException("Test case failed.", TestBlockException);
143143
}
144-
else if(TestBlockException is not null
144+
else if (TestBlockException is not null
145145
&& ThrowOnFinallyBlockException
146146
&& FinallyBlockExceptions.Any())
147147
{
148148
FinallyBlockExceptions.Insert(0, TestBlockException);
149149
throw new AggregateException("Test case failed and finally blocks failed.",
150150
FinallyBlockExceptions);
151151
}
152-
else if(TestBlockException is null
152+
else if (TestBlockException is null
153153
&& ThrowOnFinallyBlockException
154154
&& FinallyBlockExceptions.Any())
155155
{
@@ -198,7 +198,7 @@ private bool TryGetBlock(IServiceScope scope, Block block, out object blockInsta
198198
_ = TryBuildBlock(scope, block, out foundBlock);
199199
}
200200

201-
if(foundBlock is not null)
201+
if (foundBlock is not null)
202202
{
203203
blockInstance = foundBlock;
204204
result = true;
@@ -232,7 +232,7 @@ private bool TryBuildBlock(IServiceScope scope, Block block, out object? blockIn
232232
object? obj = ActivateObject(scope, block, c.ParameterType, "constructor argument");
233233
if (obj is null)
234234
{
235-
if(!CheckForITestLogger(c.ParameterType))
235+
if (!CheckForITestLogger(c.ParameterType))
236236
{
237237
blockInstance = null;
238238
return false;
@@ -266,7 +266,7 @@ private bool TrySetBlockProperties(IServiceScope scope, Block block, object bloc
266266
object? obj = ActivateObject(scope, block, prop.PropertyType, "property");
267267
if (obj is null)
268268
{
269-
if(CheckForITestLogger(prop.PropertyType))
269+
if (CheckForITestLogger(prop.PropertyType))
270270
{
271271
continue;
272272
}
@@ -287,12 +287,12 @@ private bool TryGetExecuteArguments(IServiceScope scope, Block block, out List<o
287287
foreach (ParameterInfo? ep in block.ExecuteParams)
288288
{
289289
object? obj = null;
290-
if(block.ExecuteArgumentOverrides.Count > 0)
290+
if (block.ExecuteArgumentOverrides.Count > 0)
291291
{
292292
block.ExecuteArgumentOverrides.TryGetValue(ep.ParameterType, out obj);
293293
}
294294

295-
if(obj is null)
295+
if (obj is null)
296296
{
297297
obj = ActivateObject(scope, block, ep.ParameterType, "execute method argument");
298298
if (obj is null)
@@ -324,9 +324,9 @@ private bool TryGetExecuteArguments(IServiceScope scope, Block block, out List<o
324324
// Is the below check worth it?
325325
// It is avoided if the test block asks for an interface if the dependency is implementing an interface.
326326
// HOWEVER, this would facilitate injecting multiple different implementations in a test.
327-
if(obj is null)
327+
if (obj is null)
328328
{
329-
foreach(var i in objectType.GetInterfaces())
329+
foreach (var i in objectType.GetInterfaces())
330330
{
331331
IEnumerable<object?> objs = scope.ServiceProvider.GetServices(i);
332332
obj = objs.FirstOrDefault(o => o?.GetType() == objectType);
@@ -379,7 +379,7 @@ private async Task<bool> TryRunBlock(Block block, object blockInstance, List<obj
379379
{
380380
dynamic asyncOutput = block.ExecuteMethod.Invoke(blockInstance, executeArgs.ToArray());
381381
await asyncOutput;
382-
if(block.AsyncReturnType != typeof(void))
382+
if (block.AsyncReturnType != typeof(void))
383383
{
384384
output = asyncOutput.GetAwaiter().GetResult();
385385
}
@@ -388,12 +388,27 @@ private async Task<bool> TryRunBlock(Block block, object blockInstance, List<obj
388388
{
389389
output = block.ExecuteMethod.Invoke(blockInstance, executeArgs.ToArray());
390390
}
391-
391+
392392
if (output is not null)
393393
{
394-
Log?.TestBlockOutput(output);
395-
BlockOutput.Remove(output.GetType());
396-
BlockOutput.Add(output.GetType(), output);
394+
if (output is IBlockData blockData)
395+
{
396+
foreach (KeyValuePair<Type, object?> dataPoint in blockData.Data)
397+
{
398+
if (dataPoint.Value is not null)
399+
{
400+
Log?.TestBlockOutput(dataPoint.Value);
401+
BlockOutput.Remove(dataPoint.Key);
402+
BlockOutput.Add(dataPoint.Key, dataPoint.Value);
403+
}
404+
}
405+
}
406+
else
407+
{
408+
Log?.TestBlockOutput(output);
409+
BlockOutput.Remove(output.GetType());
410+
BlockOutput.Add(output.GetType(), output);
411+
}
397412
}
398413
result = true;
399414
}

0 commit comments

Comments
 (0)