diff --git a/TestStack.BDDfy.Tests/Scanner/FluentScanner/ExpressionExtensionsTests.cs b/TestStack.BDDfy.Tests/Scanner/FluentScanner/ExpressionExtensionsTests.cs index 38cb16a0..056fb917 100644 --- a/TestStack.BDDfy.Tests/Scanner/FluentScanner/ExpressionExtensionsTests.cs +++ b/TestStack.BDDfy.Tests/Scanner/FluentScanner/ExpressionExtensionsTests.cs @@ -29,21 +29,40 @@ protected string[] InheritedArrayInput2 public class ExpressionExtensionsTests : BaseClass { + private class ContainerType + { + public int Target { get; set; } + + public string Target2 { get; set; } + + public ContainerType SubContainer { get; set; } + + public override string ToString() + { + return Target2; + } + } + class ClassUnderTest { public void MethodWithoutArguments() { - + } public void MethodWithInputs(int input1, string input2) { - + } public void MethodWithArrayInputs(int[] input1, string[] input2) { - + + } + + public void MethodWithInputs(ContainerType subContainer) + { + } } @@ -89,6 +108,8 @@ string GetInput2(string someInput) return someInput + " Input 2"; } + ContainerType container = new ContainerType(); + [Test] public void NoArguments() { @@ -96,11 +117,13 @@ public void NoArguments() Assert.That(arguments.Length, Is.EqualTo(0)); } - void AssertReturnedArguments(object[] arguments, object expectedArg1, object expectedArg2) + void AssertReturnedArguments(object[] arguments, params object[] expectedArgs) { - Assert.That(arguments.Length, Is.EqualTo(2)); - Assert.That(arguments[0], Is.EqualTo(expectedArg1)); - Assert.That(arguments[1], Is.EqualTo(expectedArg2)); + Assert.That(arguments.Length, Is.EqualTo(expectedArgs.Length)); + for (int i = 0; i < expectedArgs.Length; i++) + { + Assert.That(arguments[i], Is.EqualTo(expectedArgs[i])); + } } [Test] @@ -132,14 +155,14 @@ public void InputArgumentsProvidedUsingProperty() var arguments = GetArguments(x => x.MethodWithInputs(Input1, Input2), new ClassUnderTest()); AssertReturnedArguments(arguments, Input1, Input2); } - + [Test] public void InputArgumentsProvidedUsingInheritedFields() { var arguments = GetArguments(x => x.MethodWithInputs(InheritedInput1, InheritedInput2), new ClassUnderTest()); AssertReturnedArguments(arguments, InheritedInput1, InheritedInput2); } - + [Test] public void InputArgumentsProvidedUsingMethodCallDoesNotThrow() { @@ -150,14 +173,14 @@ public void InputArgumentsProvidedUsingMethodCallDoesNotThrow() public void ArrayInputsArgumentsProvidedInline() { var arguments = GetArguments(x => x.MethodWithArrayInputs(new[] { 1, 2 }, new[] { "3", "4" }), new ClassUnderTest()); - AssertReturnedArguments(arguments, new[] {1, 2}, new[] {"3", "4"}); + AssertReturnedArguments(arguments, new[] { 1, 2 }, new[] { "3", "4" }); } [Test] public void ArrayInputArgumentsProvidedUsingVariables() { - var input1 = new[] {1, 2}; - var input2 = new[] {"3", "4"}; + var input1 = new[] { 1, 2 }; + var input2 = new[] { "3", "4" }; var arguments = GetArguments(x => x.MethodWithArrayInputs(input1, input2), new ClassUnderTest()); AssertReturnedArguments(arguments, input1, input2); } @@ -176,6 +199,33 @@ public void ArrayInputArgumentsProvidedUsingProperty() AssertReturnedArguments(arguments, ArrayInput1, ArrayInput2); } + [Test] + public void ComplexArgument() + { + container.Target = 1; + container.SubContainer = new ContainerType { Target2 = "Foo" }; + + var arguments = GetArguments(x => x.MethodWithInputs(container.Target, container.SubContainer.Target2), new ClassUnderTest()); + AssertReturnedArguments(arguments, 1, "Foo"); + } + + [Test] + public void ComplexArgument2() + { + container.SubContainer = new ContainerType { Target2 = "Foo" }; + + var arguments = GetArguments(x => x.MethodWithInputs(container.SubContainer), new ClassUnderTest()); + AssertReturnedArguments(arguments, container.SubContainer); + } + + [Test] + public void ComplexArgumentWhenContainerIsNull() + { + ContainerType nullContainer = null; + var arguments = GetArguments(x => x.MethodWithInputs(nullContainer.SubContainer), new ClassUnderTest()); + AssertReturnedArguments(arguments, new object[] { null }); + } + [Test] public void ArrayInputArgumentsProvidedUsingInheritedProperty() { diff --git a/TestStack.BDDfy.Tests/Scanner/FluentScanner/FluentWithExamples.cs b/TestStack.BDDfy.Tests/Scanner/FluentScanner/FluentWithExamples.cs index 86750423..da8d6e04 100644 --- a/TestStack.BDDfy.Tests/Scanner/FluentScanner/FluentWithExamples.cs +++ b/TestStack.BDDfy.Tests/Scanner/FluentScanner/FluentWithExamples.cs @@ -1,4 +1,5 @@ -using ApprovalTests; +using System.Runtime.CompilerServices; +using ApprovalTests; using NUnit.Framework; using Shouldly; using TestStack.BDDfy.Reporters; @@ -31,6 +32,22 @@ public void FluentCanBeUsedWithExamples() Approvals.Verify(textReporter.ToString()); } + [Test] + [MethodImpl(MethodImplOptions.NoInlining)] + public void ExampleTypeMismatch() + { + var ex = Should.Throw( + () => this.Given(() => WrongType.ShouldBe(1), "Given i use an example") + .WithExamples(new ExampleTable("Wrong type") + { + new object(), + new object[] { null } + }) + .BDDfy()); + + ex.Message.ShouldBe("System.Object cannot be assigned to Int32 (Column: 'Wrong type', Row: 1)"); + } + private void AndIUseA(string multiWordHeading) { multiWordHeading.ShouldBeOneOf("", "val2"); @@ -44,7 +61,7 @@ private void GivenADifferentMethodWith(string prop2) private void GivenADifferentMethodWithRandomArg(int foo) { - + } private void ThenAllIsGood() @@ -63,6 +80,7 @@ private void GivenMethodTaking__ExampleInt__(int exampleInt) exampleInt.ShouldBeInRange(1, 2); } + public int WrongType { get; set; } public int Prop1 { get; set; } private string _prop2 = null; private string multiWordHeading = null; diff --git a/TestStack.BDDfy.Tests/Scanner/StepScanners/Examples/ExampleValueTests.cs b/TestStack.BDDfy.Tests/Scanner/StepScanners/Examples/ExampleValueTests.cs new file mode 100644 index 00000000..28b0acde --- /dev/null +++ b/TestStack.BDDfy.Tests/Scanner/StepScanners/Examples/ExampleValueTests.cs @@ -0,0 +1,18 @@ +using System; +using NUnit.Framework; +using Shouldly; + +namespace TestStack.BDDfy.Tests.Scanner.StepScanners.Examples +{ + public class ExampleValueTests + { + [Test] + public void CanFormatAsStringTests() + { + new ExampleValue("Header", null, () => 0).GetValueAsString().ShouldBe("'null'"); + new ExampleValue("Header", 1, () => 0).GetValueAsString().ShouldBe("1"); + new ExampleValue("Header", new Object(), () => 0).GetValueAsString().ShouldBe("System.Object"); + new ExampleValue("Header", new[] {1, 2}, () => 0).GetValueAsString().ShouldBe("1, 2"); + } + } +} \ No newline at end of file diff --git a/TestStack.BDDfy.Tests/TestStack.BDDfy.Tests.csproj b/TestStack.BDDfy.Tests/TestStack.BDDfy.Tests.csproj index 1400d110..2d1ce722 100644 --- a/TestStack.BDDfy.Tests/TestStack.BDDfy.Tests.csproj +++ b/TestStack.BDDfy.Tests/TestStack.BDDfy.Tests.csproj @@ -90,6 +90,7 @@ + diff --git a/TestStack.BDDfy/Reporters/Html/ClassicReportBuilder.cs b/TestStack.BDDfy/Reporters/Html/ClassicReportBuilder.cs index 74cd5f8a..cf43d5ee 100644 --- a/TestStack.BDDfy/Reporters/Html/ClassicReportBuilder.cs +++ b/TestStack.BDDfy/Reporters/Html/ClassicReportBuilder.cs @@ -241,7 +241,7 @@ private void AddExampleRow(Scenario scenario, Result scenarioResult) { AddLine(string.Format("", scenario.Result)); foreach (var exampleValue in scenario.Example.Values) - AddLine(string.Format("{0}", HttpUtility.HtmlEncode(exampleValue.GetValue(typeof(string))))); + AddLine(string.Format("{0}", HttpUtility.HtmlEncode(exampleValue.GetValueAsString()))); if (scenarioResult != Result.Failed) return; diff --git a/TestStack.BDDfy/Reporters/Html/MetroReportBuilder.cs b/TestStack.BDDfy/Reporters/Html/MetroReportBuilder.cs index 9c4018e4..1a0c96e2 100644 --- a/TestStack.BDDfy/Reporters/Html/MetroReportBuilder.cs +++ b/TestStack.BDDfy/Reporters/Html/MetroReportBuilder.cs @@ -284,7 +284,7 @@ private void AddExampleRow(Scenario scenario, Result scenarioResult) { AddLine(string.Format("", scenario.Result)); foreach (var exampleValue in scenario.Example.Values) - AddLine(string.Format("{0}", HttpUtility.HtmlEncode(exampleValue.GetValue(typeof(string))))); + AddLine(string.Format("{0}", HttpUtility.HtmlEncode(exampleValue.GetValueAsString()))); if (scenarioResult != Result.Failed) return; diff --git a/TestStack.BDDfy/Reporters/TextReporter.cs b/TestStack.BDDfy/Reporters/TextReporter.cs index 30cc176a..1793ce70 100644 --- a/TestStack.BDDfy/Reporters/TextReporter.cs +++ b/TestStack.BDDfy/Reporters/TextReporter.cs @@ -97,7 +97,7 @@ private void WriteExamples(Scenario exampleScenario, IEnumerable scena ? null : string.Format("Step: {0} failed with exception: {1}", failingStep.Title, CreateExceptionMessage(failingStep)); - addRow(scenario.Example.Values.Select(e => (string)e.GetValue(typeof(string))), scenario.Result.ToString(), error); + addRow(scenario.Example.Values.Select(e => e.GetValueAsString()), scenario.Result.ToString(), error); } foreach (var row in rows) diff --git a/TestStack.BDDfy/Scanners/StepScanners/ArgumentCleaningExtensions.cs b/TestStack.BDDfy/Scanners/StepScanners/ArgumentCleaningExtensions.cs new file mode 100644 index 00000000..4564c2f8 --- /dev/null +++ b/TestStack.BDDfy/Scanners/StepScanners/ArgumentCleaningExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; + +namespace TestStack.BDDfy +{ + internal static class ArgumentCleaningExtensions + { + internal static object[] FlattenArrays(this object[] inputs) + { + return inputs.Select(FlattenArray).ToArray(); + } + + public static object FlattenArray(this object input) + { + var inputArray = input as Array; + if (inputArray != null) + { + var temp = (from object arrElement in inputArray select GetSafeString(arrElement)).ToArray(); + return string.Join(", ", temp); + } + + if (input == null) return "'null'"; + + return input; + } + + static string GetSafeString(object input) + { + if (input == null) + return "'null'"; + + return input.ToString(); + } + } +} \ No newline at end of file diff --git a/TestStack.BDDfy/Scanners/StepScanners/Examples/ExampleValue.cs b/TestStack.BDDfy/Scanners/StepScanners/Examples/ExampleValue.cs index d86cc03b..cfcc8bde 100644 --- a/TestStack.BDDfy/Scanners/StepScanners/Examples/ExampleValue.cs +++ b/TestStack.BDDfy/Scanners/StepScanners/Examples/ExampleValue.cs @@ -34,7 +34,7 @@ public object GetValue(Type targetType) var stringValue = _underlyingValue as string; if (_underlyingValue == null) { - if (targetType.IsValueType && !(targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof (Nullable<>))) + if (targetType.IsValueType && !(targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))) { var valueAsString = string.IsNullOrEmpty(stringValue) ? "" : string.Format("\"{0}\"", _underlyingValue); throw new ArgumentException(string.Format("Cannot convert {0} to {1} (Column: '{2}', Row: {3})", valueAsString, targetType.Name, Header, Row)); @@ -49,10 +49,20 @@ public object GetValue(Type targetType) if (targetType.IsEnum && _underlyingValue is string) return Enum.Parse(targetType, (string)_underlyingValue); - if (targetType == typeof (DateTime)) + if (targetType == typeof(DateTime)) return DateTime.Parse(stringValue); - return Convert.ChangeType(_underlyingValue, targetType); + try + { + return Convert.ChangeType(_underlyingValue, targetType); + } + catch (InvalidCastException ex) + { + throw new UnassignableExampleException(string.Format( + "{0} cannot be assigned to {1} (Column: '{2}', Row: {3})", + _underlyingValue == null ? "" : _underlyingValue.ToString(), + targetType.Name, Header, Row), ex, this); + } } public bool ValueHasBeenUsed { get; private set; } @@ -61,5 +71,10 @@ public override string ToString() { return string.Join("{0}: {1}", Header, _underlyingValue); } + + public string GetValueAsString() + { + return _underlyingValue.FlattenArray().ToString(); + } } } \ No newline at end of file diff --git a/TestStack.BDDfy/Scanners/StepScanners/Examples/UnassignableExampleException.cs b/TestStack.BDDfy/Scanners/StepScanners/Examples/UnassignableExampleException.cs new file mode 100644 index 00000000..f374c272 --- /dev/null +++ b/TestStack.BDDfy/Scanners/StepScanners/Examples/UnassignableExampleException.cs @@ -0,0 +1,22 @@ +using System; +using System.Runtime.Serialization; + +namespace TestStack.BDDfy +{ + [Serializable] + public class UnassignableExampleException : Exception + { + public UnassignableExampleException(string message, Exception inner, ExampleValue exampleValue) : base(message, inner) + { + ExampleValue = exampleValue; + } + + protected UnassignableExampleException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + + public ExampleValue ExampleValue { get; private set; } + } +} \ No newline at end of file diff --git a/TestStack.BDDfy/Scanners/StepScanners/Fluent/ExpressionExtensions.cs b/TestStack.BDDfy/Scanners/StepScanners/Fluent/ExpressionExtensions.cs index edad581a..afe96c04 100644 --- a/TestStack.BDDfy/Scanners/StepScanners/Fluent/ExpressionExtensions.cs +++ b/TestStack.BDDfy/Scanners/StepScanners/Fluent/ExpressionExtensions.cs @@ -152,7 +152,6 @@ private static IEnumerable ExtractConvertibleTypeArrayConstants(NewArray private static IEnumerable ExtractArguments(ConstantExpression constantExpression, T value) { - var expression = constantExpression.Value as Expression; if (expression != null) { @@ -220,7 +219,26 @@ private static IEnumerable ExtractConstant(MemberExpression memberExpres private static IEnumerable ExtractPropertyValue(MemberExpression expression, PropertyInfo member, T value) { - return new[] { member.GetValue(value, null) }; + var memberExpression = expression.Expression as MemberExpression; + if (memberExpression != null) + { + var extractArguments = ExtractArguments(memberExpression, value); + try + { + return new[] + { + member.GetValue(extractArguments.Single(), null) + }; + } + catch (TargetException) + { + return new object[] {null}; + } + } + return new[] + { + member.GetValue(value, null) + }; } } -} +} \ No newline at end of file diff --git a/TestStack.BDDfy/Scanners/StepScanners/StepScannerExtensions.cs b/TestStack.BDDfy/Scanners/StepScanners/StepScannerExtensions.cs deleted file mode 100644 index 45f20e4b..00000000 --- a/TestStack.BDDfy/Scanners/StepScanners/StepScannerExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace TestStack.BDDfy -{ - internal static class StepScannerExtensions - { - internal static object[] FlattenArrays(this object[] inputs) - { - var flatArray = new List(); - foreach (var input in inputs) - { - var inputArray = input as Array; - if (inputArray != null) - { - var temp = (from object arrElement in inputArray select GetSafeString(arrElement)).ToArray(); - flatArray.Add(string.Join(", ", temp)); - } - else if (input == null) - flatArray.Add("'null'"); - else - flatArray.Add(input); - } - - return flatArray.ToArray(); - } - - static string GetSafeString(object input) - { - if (input == null) - return "'null'"; - - return input.ToString(); - } - } -} \ No newline at end of file diff --git a/TestStack.BDDfy/TestStack.BDDfy.csproj b/TestStack.BDDfy/TestStack.BDDfy.csproj index f63b56f3..67d992fd 100644 --- a/TestStack.BDDfy/TestStack.BDDfy.csproj +++ b/TestStack.BDDfy/TestStack.BDDfy.csproj @@ -114,6 +114,7 @@ + True True @@ -161,7 +162,7 @@ - +