diff --git a/samples/FluentAssertions.AspNetCore.Mvc.Sample.Tests/ProductController_Tests.cs b/samples/FluentAssertions.AspNetCore.Mvc.Sample.Tests/ProductController_Tests.cs index f0c984f..2f7450f 100644 --- a/samples/FluentAssertions.AspNetCore.Mvc.Sample.Tests/ProductController_Tests.cs +++ b/samples/FluentAssertions.AspNetCore.Mvc.Sample.Tests/ProductController_Tests.cs @@ -1,6 +1,5 @@ using FluentAssertions.AspNetCore.Mvc.Sample.Controllers; using Microsoft.AspNetCore.Mvc; -using System; using Xunit; namespace FluentAssertions.AspNetCore.Mvc.Sample.Tests @@ -25,8 +24,10 @@ public void GetActionResultOfT_OnFalse_Returns_Data() var result = controller.GetActionResultOfT(model, returnError); - result.Should().BeConvertibleTo() - .And.Value.Should().BeSameAs(model); + result.Should().BeObjectResult() + .WithValue(model) // Equals check + .WithValueEquivalentTo(model) // Equivalency check + .WithValueMatch(m => m.Id == 1); // match check. } [Fact] diff --git a/src/FluentAssertions.AspNetCore.Mvc/ActionResultAssertions.cs b/src/FluentAssertions.AspNetCore.Mvc/ActionResultAssertions.cs index 47466f1..f9a589d 100644 --- a/src/FluentAssertions.AspNetCore.Mvc/ActionResultAssertions.cs +++ b/src/FluentAssertions.AspNetCore.Mvc/ActionResultAssertions.cs @@ -139,7 +139,7 @@ public FileStreamResultAssertions BeFileStreamResult(string reason = "", params } /// - /// Asserts that the subject is an . + /// Asserts that the subject is an . /// /// /// A formatted phrase as is supported by explaining why the assertion diff --git a/src/FluentAssertions.AspNetCore.Mvc/ActionResultAssertionsOfTValue.cs b/src/FluentAssertions.AspNetCore.Mvc/ActionResultAssertionsOfTValue.cs index 5582e44..0b6e610 100644 --- a/src/FluentAssertions.AspNetCore.Mvc/ActionResultAssertionsOfTValue.cs +++ b/src/FluentAssertions.AspNetCore.Mvc/ActionResultAssertionsOfTValue.cs @@ -79,6 +79,25 @@ public AndWhichConstraint, TActionResult> BeConve return new AndWhichConstraint, TActionResult>(this, (TActionResult)convertResult); } + + /// + /// Asserts that the is type of . + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + [CustomAssertion] + public ObjectResultAssertions BeObjectResult(string reason = "", params object[] reasonArgs) + { + var result = BeConvertibleTo(reason, reasonArgs).Which; + + return new ObjectResultAssertions(result); + } + + #endregion Public Methods } - #endregion Public Methods } diff --git a/src/FluentAssertions.AspNetCore.Mvc/FluentAssertions.AspNetCore.Mvc.csproj b/src/FluentAssertions.AspNetCore.Mvc/FluentAssertions.AspNetCore.Mvc.csproj index 4222a96..4899ad1 100644 --- a/src/FluentAssertions.AspNetCore.Mvc/FluentAssertions.AspNetCore.Mvc.csproj +++ b/src/FluentAssertions.AspNetCore.Mvc/FluentAssertions.AspNetCore.Mvc.csproj @@ -5,7 +5,7 @@ Copyright 2018 Fluent Assertions extensions for ASP.NET Core MVC Fluent Assertions for ASP.NET Core MVC - 3.1.0 + 3.2.0 Casey Burns;Kevin Kuszyk netstandard2.0;netcoreapp3.0 FluentAssertions.AspNetCore.Mvc diff --git a/src/FluentAssertions.AspNetCore.Mvc/ObjectResultAssertionsBase.cs b/src/FluentAssertions.AspNetCore.Mvc/ObjectResultAssertionsBase.cs index 25a76a4..15a2048 100644 --- a/src/FluentAssertions.AspNetCore.Mvc/ObjectResultAssertionsBase.cs +++ b/src/FluentAssertions.AspNetCore.Mvc/ObjectResultAssertionsBase.cs @@ -1,4 +1,6 @@ -using FluentAssertions.Execution; +using FluentAssertions.Common; +using FluentAssertions.Equivalency; +using FluentAssertions.Execution; using FluentAssertions.Primitives; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Formatters; @@ -157,14 +159,108 @@ public TObjectResultAssertion WithDeclaredType(Type expectedDeclaredType, string var actual = ObjectResultSubject.DeclaredType; Execute.Assertion + .BecauseOf(reason, reasonArgs) .ForCondition(expectedDeclaredType == actual) .WithDefaultIdentifier(Identifier + ".DeclaredType") - .BecauseOf(reason, reasonArgs) .FailWith(FailureMessages.CommonTypeFailMessage, expectedDeclaredType, actual); return (TObjectResultAssertion)this; } + /// + /// Asserts that the is the expected value. + /// + /// The expected value. + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public TObjectResultAssertion WithValue(object expectedValue, string reason = "", params object[] reasonArgs) + { + object actualValue = ObjectResultSubject.Value; + + Execute.Assertion + .BecauseOf(reason, reasonArgs) + .ForCondition(actualValue.IsSameOrEqualTo(expectedValue)) + .WithDefaultIdentifier(Identifier + ".Value") + .FailWith(FailureMessages.CommonFailMessage, expectedValue, actualValue); + + return (TObjectResultAssertion)this; + } + + /// + /// Asserts that the is equivalent to another object. + /// + /// The expected value. + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public TObjectResultAssertion WithValueEquivalentTo(TExpectation expectation, + string reason = "", params object[] reasonArgs) + { + return WithValueEquivalentTo(expectation, config => config, reason, reasonArgs); + } + + /// + /// Asserts that the is equivalent to another object. + /// + /// The expected status code. + /// + /// A reference to the configuration object that can be used + /// to influence the way the object graphs are compared. You can also provide an alternative instance of the + /// class. The global defaults are determined by the + /// class. + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public TObjectResultAssertion WithValueEquivalentTo(TExpectation expectation, + Func, EquivalencyAssertionOptions> config, string reason = "", params object[] reasonArgs) + { + object actualValue = ObjectResultSubject.Value; + + actualValue.Should().BeEquivalentTo(expectation, config, reason, reasonArgs); + + return (TObjectResultAssertion)this; + } + + + /// + /// Asserts that the statisfies the . + /// + /// + /// The predicate which must be satisfied by the . + /// + /// + /// A formatted phrase as is supported by explaining why the assertion + /// is needed. If the phrase does not start with the word because, it is prepended automatically. + /// + /// + /// Zero or more objects to format using the placeholders in . + /// + public TObjectResultAssertion WithValueMatch(Expression> predicate, + string reason = "", params object[] reasonArgs) + { + object actualValue = ValueAs(); + + using(var scope = new AssertionScope(Identifier + ".Value")) + { + actualValue.Should().Match(predicate, reason, reasonArgs); + } + + return (TObjectResultAssertion)this; + } /// /// Asserts that the is the expected status code. @@ -182,9 +278,9 @@ public TObjectResultAssertion WithStatusCode(int? expectedStatusCode, string rea var actual = ObjectResultSubject.StatusCode; Execute.Assertion + .BecauseOf(reason, reasonArgs) .ForCondition(expectedStatusCode == actual) .WithDefaultIdentifier(Identifier + ".StatusCode") - .BecauseOf(reason, reasonArgs) .FailWith(FailureMessages.CommonFailMessage, expectedStatusCode, actual); return (TObjectResultAssertion)this; diff --git a/tests/FluentAssertions.AspNetCore.Mvc.Tests/ActionResultAssertionsOfTValue_Tests.cs b/tests/FluentAssertions.AspNetCore.Mvc.Tests/ActionResultAssertionsOfTValue_Tests.cs index 0614f45..b092c79 100644 --- a/tests/FluentAssertions.AspNetCore.Mvc.Tests/ActionResultAssertionsOfTValue_Tests.cs +++ b/tests/FluentAssertions.AspNetCore.Mvc.Tests/ActionResultAssertionsOfTValue_Tests.cs @@ -1,6 +1,5 @@ using FluentAssertions.Mvc.Tests.Helpers; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; using System; using Xunit; @@ -72,6 +71,28 @@ public void BeConvertibleTo_ShouldBeTheConvertedObject() actual.Should().BeSameAs(expectation); } + + [Fact] + public void BeObjectResult_GivenActionResultWithObjectResult_ShouldPass() + { + var result = new ActionResult(new object()); + + result.Should().BeObjectResult(Reason, ReasonArgs); + } + + [Fact] + public void BeObjectResult_GivenActionResultWithNotObjectResult_ShouldFail() + { + var result = new ActionResult(new BadRequestObjectResult(new object())); + var failureMessage = FailureMessageHelper.ExpectedContextToBeConvertible( + "result", typeof(ObjectResult).FullName, typeof(BadRequestObjectResult).FullName); + + Action action = () => result.Should().BeObjectResult(Reason, ReasonArgs); + + action.Should().Throw() + .WithMessage(failureMessage); + } + #endregion Public Methods } } \ No newline at end of file diff --git a/tests/FluentAssertions.AspNetCore.Mvc.Tests/ObjectResultAssertions_Tests.cs b/tests/FluentAssertions.AspNetCore.Mvc.Tests/ObjectResultAssertions_Tests.cs index 19df4e6..b357564 100644 --- a/tests/FluentAssertions.AspNetCore.Mvc.Tests/ObjectResultAssertions_Tests.cs +++ b/tests/FluentAssertions.AspNetCore.Mvc.Tests/ObjectResultAssertions_Tests.cs @@ -12,6 +12,11 @@ namespace FluentAssertions.AspNetCore.Mvc.Tests public class ObjectResultAssertions_Tests { private const string TestValue = "testValue"; + private const string WrongValue = "wrongValue"; + + private readonly object TestObject = new { Value = "testValue" }; + private readonly object GoodObject = new { Value = "testValue" }; + private readonly object WrongObject = new { Value = "wrongValue" }; public const string Reason = FailureMessageHelper.Reason; public readonly static object[] ReasonArgs = FailureMessageHelper.ReasonArgs; @@ -219,5 +224,81 @@ public void WithStatusCode_GivenUnexpected_ShouldFail() a.Should().Throw() .WithMessage(failureMessage); } + + [Fact] + public void WithValue_GivenExpected_ShouldPass() + { + var result = new ObjectResult(TestValue); + + result.Should().BeObjectResult() + .WithValue(TestValue); + } + + [Fact] + public void WithValue_GivenUnexpected_ShouldFail() + { + var result = new ObjectResult(WrongValue); + string failureMessage = FailureMessageHelper.ExpectedContextToBeXButY( + "ObjectResult.Value", + TestValue, + WrongValue); + + Action a = () => result.Should().BeObjectResult().WithValue(TestValue, Reason, ReasonArgs); + + a.Should().Throw() + .WithMessage(failureMessage); + } + + [Fact] + public void WithValueEquivalentTo_GivenExpected_ShouldPass() + { + var result = new ObjectResult(TestObject); + + result.Should().BeObjectResult() + .WithValueEquivalentTo(GoodObject); + } + + [Fact] + public void WithValueEquivalentTo_GivenUnexpected_ShouldFail() + { + var result = new ObjectResult(WrongObject); + string failureMessage = @"Expected member Value to be +""testValue"" with a length of 9 because it is 10, but +""wrongValue"" has a length of 10. + +With configuration: +- Use declared types and members +- Compare enums by value +- Match member by name (or throw) +- Without automatic conversion. +- Be strict about the order of items in byte arrays"; + + Action a = () => result.Should().BeObjectResult().WithValueEquivalentTo(GoodObject, Reason, ReasonArgs); + + a.Should().Throw() + .WithMessage(failureMessage); + } + + [Fact] + public void WithValueMatch_GivenExpected_ShouldPass() + { + var result = new ObjectResult(TestValue); + + result.Should().BeObjectResult() + .WithValueMatch(value => value == TestValue); + } + + [Fact] + public void WithValueMatch_GivenUnexpected_ShouldFail() + { + var result = new ObjectResult(WrongValue); + string failureMessage = "Expected ObjectResult.Value to match (value == \"testValue\") because it is 10, but found \"wrongValue\"."; + + Action a = () => result.Should().BeObjectResult().WithValueMatch(value => value == TestValue, Reason, ReasonArgs); + + a.Should().Throw() + .WithMessage(failureMessage); + } + } }