diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7d4e175544..890a5ff95e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,10 +2,10 @@ name: build on: pull_request: - paths-ignore: + paths-ignore: - docs/** push: - paths-ignore: + paths-ignore: - docs/** jobs: @@ -17,37 +17,32 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 - + - name: Setup .NET 5 uses: actions/setup-dotnet@v1 with: dotnet-version: 5.0.x - + - name: Setup .NET 3.1 uses: actions/setup-dotnet@v1 with: dotnet-version: 3.1.x - + - name: Setup .NET 2.1 uses: actions/setup-dotnet@v1 with: dotnet-version: 2.1.x - - - name: Install NUKE - run: | - dotnet tool install --global Nuke.GlobalTool - + - name: Run NUKE - run: nuke + run: ./build.ps1 + env: + BranchSpec: ${{ github.ref }} + BuildNumber: ${{ github.run_number}} + ApiKey: ${{ secrets.NUGETAPIKEY }} - name: Upload artifacts uses: actions/upload-artifact@v2 with: path: ./Artifacts/* - - - name: Push to Nuget - run: dotnet nuget push .\artifacts\*.nupkg --api-key "$env:ApiKey" --skip-duplicate --no-symbols true --source https://api.nuget.org/v3/index.json - if: contains(github.ref, 'refs/tags/') - env: - ApiKey: ${{ secrets.NUGETAPIKEY }} - + + diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index a2c28c228d..e85daa3006 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -6,6 +6,18 @@ "build": { "type": "object", "properties": { + "ApiKey": { + "type": "string", + "description": "The key to push to Nuget" + }, + "BranchSpec": { + "type": "string", + "description": "A branch specification such as develop or refs/pull/1775/merge" + }, + "BuildNumber": { + "type": "string", + "description": "An incrementing build number as provided by the build engine" + }, "Continue": { "type": "boolean", "description": "Indicates to continue a previously failed build attempt" @@ -64,9 +76,11 @@ "type": "string", "enum": [ "ApiChecks", + "CalculateNugetVersion", "Clean", "Compile", "Pack", + "Push", "Restore", "TestFrameworks", "UnitTests" @@ -84,9 +98,11 @@ "type": "string", "enum": [ "ApiChecks", + "CalculateNugetVersion", "Clean", "Compile", "Pack", + "Push", "Restore", "TestFrameworks", "UnitTests" diff --git a/AcceptApiChanges.sh b/AcceptApiChanges.sh old mode 100644 new mode 100755 diff --git a/Build/.editorconfig b/Build/.editorconfig index 21da7492e9..5641e0f6df 100644 --- a/Build/.editorconfig +++ b/Build/.editorconfig @@ -9,5 +9,8 @@ csharp_style_expression_bodied_properties = true:warning csharp_style_expression_bodied_indexers = true:warning csharp_style_expression_bodied_accessors = true:warning -dotnet_diagnostic.IDE0044.severity = none -dotnet_diagnostic.IDE0051.severity = none +dotnet_diagnostic.ide0044.severity = none +dotnet_diagnostic.ide0051.severity = none + +# ReSharper properties +resharper_place_field_attribute_on_same_line = false diff --git a/Build/Build.cs b/Build/Build.cs index 29b8e4ee15..a09090bf5a 100644 --- a/Build/Build.cs +++ b/Build/Build.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Linq; using Nuke.Common; using Nuke.Common.Execution; @@ -24,20 +26,54 @@ class Build : NukeBuild - Microsoft VSCode https://nuke.build/vscode */ - public static int Main() => Execute(x => x.Pack); + public static int Main() => Execute(x => x.Push); - [Solution(GenerateProjects = true)] readonly Solution Solution; - [GitVersion(Framework = "net5.0")] readonly GitVersion GitVersion; - [PackageExecutable("nspec", "NSpecRunner.exe", Version = "3.1.0")] Tool NSpec3; + [Parameter("A branch specification such as develop or refs/pull/1775/merge")] + readonly string BranchSpec; + + [Parameter("An incrementing build number as provided by the build engine")] + readonly string BuildNumber; + + [Parameter("The key to push to Nuget")] + readonly string ApiKey; + + [Solution(GenerateProjects = true)] + readonly Solution Solution; + + [GitVersion(Framework = "net5.0")] + readonly GitVersion GitVersion; + + [PackageExecutable("nspec", "NSpecRunner.exe", Version = "3.1.0")] + Tool NSpec3; AbsolutePath ArtifactsDirectory => RootDirectory / "Artifacts"; + string SemVer; + Target Clean => _ => _ .Executes(() => { EnsureCleanDirectory(ArtifactsDirectory); }); + Target CalculateNugetVersion => _ => _ + .Executes(() => + { + SemVer = GitVersion.SemVer; + if (IsPullRequest) + { + Serilog.Log.Information( + "Branch spec {branchspec} is a pull request. Adding build number {buildnumber}", + BranchSpec, BuildNumber); + + SemVer = string.Join('.', GitVersion.SemVer.Split('.').Take(3).Union(new [] { BuildNumber })); + } + + Serilog.Log.Information("SemVer = {semver}", SemVer); + }); + + bool IsPullRequest => BranchSpec != null && BranchSpec.Contains("pull", StringComparison.InvariantCultureIgnoreCase); + Target Restore => _ => _ .DependsOn(Clean) .Executes(() => @@ -78,11 +114,18 @@ class Build : NukeBuild { if (EnvironmentInfo.IsWin) { - Xunit2(s => s - .SetFramework("net47") - .AddTargetAssemblies(GlobFiles( + Xunit2(s => + { + IReadOnlyCollection testAssemblies = GlobFiles( Solution.Specs.FluentAssertions_Specs.Directory, - "bin/Debug/net47/*.Specs.dll").NotEmpty())); + "bin/Debug/net47/*.Specs.dll"); + + Assert.NotEmpty(testAssemblies.ToList()); + + return s + .SetFramework("net47") + .AddTargetAssemblies(testAssemblies); + }); } DotNetTest(s => s @@ -128,6 +171,7 @@ from framework in supportedFrameworks .DependsOn(ApiChecks) .DependsOn(TestFrameworks) .DependsOn(UnitTests) + .DependsOn(CalculateNugetVersion) .Executes(() => { DotNetPack(s => s @@ -135,6 +179,27 @@ from framework in supportedFrameworks .SetOutputDirectory(ArtifactsDirectory) .SetConfiguration("Release") .EnableContinuousIntegrationBuild() // Necessary for deterministic builds - .SetVersion(GitVersion.NuGetVersionV2)); + .SetVersion(SemVer)); }); + + Target Push => _ => _ + .DependsOn(Pack) + .OnlyWhenDynamic(() => IsTag) + .Executes(() => + { + var packages = GlobFiles(ArtifactsDirectory, "*.nupkg"); + + Assert.NotEmpty(packages.ToList()); + + DotNetNuGetPush(s => s + .SetApiKey(ApiKey) + .EnableSkipDuplicate() + .SetSource("https://api.nuget.org/v3/index.json") + .EnableNoSymbols() + .CombineWith(packages, + (v, path) => v.SetTargetPath(path))); + }); + + bool IsTag => BranchSpec != null && BranchSpec.Contains("refs/tags", StringComparison.InvariantCultureIgnoreCase); + } diff --git a/Build/_build.csproj b/Build/_build.csproj index b2677c5693..7ad25ada24 100644 --- a/Build/_build.csproj +++ b/Build/_build.csproj @@ -8,9 +8,9 @@ ..\ - + - + diff --git a/FluentAssertions.sln b/FluentAssertions.sln index 3973759a76..c69957349c 100644 --- a/FluentAssertions.sln +++ b/FluentAssertions.sln @@ -42,6 +42,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Approval.Tests", "Tests\App EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentAssertions.Specs", "Tests\FluentAssertions.Specs\FluentAssertions.Specs.csproj", "{6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UWP.Specs", "Tests\UWP.Specs\UWP.Specs.csproj", "{08087654-2C32-4818-95E4-45362373441D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CI|Any CPU = CI|Any CPU @@ -117,6 +119,9 @@ Global {6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD}.Debug|Any CPU.Build.0 = Debug|Any CPU {6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD}.Release|Any CPU.ActiveCfg = Release|Any CPU {6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD}.Release|Any CPU.Build.0 = Release|Any CPU + {08087654-2C32-4818-95E4-45362373441D}.CI|Any CPU.ActiveCfg = Debug|x64 + {08087654-2C32-4818-95E4-45362373441D}.Debug|Any CPU.ActiveCfg = Debug|x64 + {08087654-2C32-4818-95E4-45362373441D}.Release|Any CPU.ActiveCfg = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -133,6 +138,7 @@ Global {FCAFB0F1-79EA-4D49-813B-188D4BC4BE71} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} {F5115158-A038-4D14-A04E-46E7863E40B9} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} {6D31FFF8-E7FD-41D2-996C-CA8DDFDAE4FD} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} + {08087654-2C32-4818-95E4-45362373441D} = {963262D0-9FD5-4741-8C0E-E2F34F110EF3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {75DDA3D8-9D6F-4865-93F4-DDE11DEE8290} diff --git a/GitVersion.yml b/GitVersion.yml index aec62da423..99fad930de 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -3,5 +3,11 @@ branches: release: regex: releases?[/-] tag: rc + pull-request: + mode: ContinuousDeployment + regex: ((pull|pull\-requests|pr)[/-]|[/-](merge)) + tag: pr + tag-number-pattern: '[/-]?(?\d+)' + prevent-increment-of-merged-branch-version: false ignore: sha: [] diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 6c9fc5255c..b4449db0ce 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -3,7 +3,7 @@ * Tried upgrading to newest version of Fluent Assertions, to see if your issue has already been resolved and released? * Checked existing open *and* closed [issues](https://github.com/fluentassertions/fluentassertions/issues?utf8=%E2%9C%93&q=is%3Aissue), to see if the issue has already been reported? * Tried reproducing your problem in a new isolated project? -* Read the [documentation](https://fluentassertions.com/introduction/)? +* Read the [documentation](https://fluentassertions.com/introduction)? * Considered if this is a general question and not a bug?. For general questions please use [StackOverflow](https://stackoverflow.com/questions/tagged/fluent-assertions?mixed=1). ### Description @@ -43,4 +43,4 @@ result.Should().Be('M'); ### Additional Information -Any additional information, configuration or data that might be necessary to reproduce the issue. \ No newline at end of file +Any additional information, configuration or data that might be necessary to reproduce the issue. diff --git a/README.md b/README.md index 03c59e98de..a8c1e9ca5f 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ See https://www.fluentassertions.com for [background information](https://fluent Originally authored by Dennis Doomen with Jonas Nyrup as the productive side-kick. Notable contributions were provided by Artur Krajewski, Lukas Grützmacher and David Omid. # How do I build this? -Install Visual Studio 2019 16.9+ or JetBrains Rider 2017.1 and Build Tools 2017 and run. -You will also need to have .NET Framework 4.7 SDK and .NET 5.0 SDK installed - check [global.json](global.json) for the current minimum required version. +Install Visual Studio 2019 16.9+ or JetBrains Rider 2021.1.0 as well as the Build Tools 2019 (including the Universal Windows Platform build tools). +You will also need to have .NET Framework 4.7 SDK and .NET 5.0 SDK installed. Check [global.json](global.json) for the current minimum required version. # What are these Approval.Tests? This is a special set of tests that use the [Verify](https://github.com/VerifyTests/Verify) project to verify whether you've introduced any breaking changes in the public API of the library. diff --git a/Src/FluentAssertions/AssertionExtensions.cs b/Src/FluentAssertions/AssertionExtensions.cs index 9eb3e85dfc..4940cffe48 100644 --- a/Src/FluentAssertions/AssertionExtensions.cs +++ b/Src/FluentAssertions/AssertionExtensions.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.IO; using System.Linq.Expressions; +using System.Net.Http; using System.Reflection; using System.Threading.Tasks; using System.Xml.Linq; @@ -302,6 +303,16 @@ public static NullableBooleanAssertions Should(this bool? actualValue) return new NullableBooleanAssertions(actualValue); } + /// + /// Returns an object that can be used to assert the + /// current . + /// + [Pure] + public static HttpResponseMessageAssertions Should(this HttpResponseMessage actualValue) + { + return new HttpResponseMessageAssertions(actualValue); + } + /// /// Returns an object that can be used to assert the /// current . diff --git a/Src/FluentAssertions/CallerIdentifier.cs b/Src/FluentAssertions/CallerIdentifier.cs index cbf3f2fa88..3985f68194 100644 --- a/Src/FluentAssertions/CallerIdentifier.cs +++ b/Src/FluentAssertions/CallerIdentifier.cs @@ -27,7 +27,7 @@ public static string DetermineCallerIdentity() var stack = new StackTrace(fNeedFileInfo: true); var allStackFrames = stack.GetFrames() - .Where(frame => !IsCompilerServices(frame)) + .Where(frame => frame is not null && !IsCompilerServices(frame)) .ToArray(); int searchStart = allStackFrames.Length - 1; @@ -40,13 +40,11 @@ public static string DetermineCallerIdentity() frame => !IsCurrentAssembly(frame)); } - int firstFluentAssertionsCodeIndex = Array.FindLastIndex( + int lastUserStackFrameBeforeFluentAssertionsCodeIndex = Array.FindIndex( allStackFrames, - searchStart, - frame => IsCurrentAssembly(frame)); - - int lastUserStackFrameBeforeFluentAssertionsCodeIndex = - firstFluentAssertionsCodeIndex + 1; + startIndex: 0, + count: searchStart + 1, + frame => !IsCurrentAssembly(frame) && !IsDotNet(frame)); for (int i = lastUserStackFrameBeforeFluentAssertionsCodeIndex; i < allStackFrames.Length; i++) { @@ -84,7 +82,7 @@ public StackFrameReference() var stack = new StackTrace(); var allStackFrames = stack.GetFrames() - .Where(frame => !IsCompilerServices(frame)) + .Where(frame => frame is not null && !IsCompilerServices(frame)) .ToArray(); int firstUserCodeFrameIndex = 0; @@ -117,7 +115,7 @@ internal static IDisposable OverrideStackSearchUsingCurrentScope() internal static bool OnlyOneFluentAssertionScopeOnCallStack() { var allStackFrames = new StackTrace().GetFrames() - .Where(frame => !IsCompilerServices(frame)) + .Where(frame => frame is not null && !IsCompilerServices(frame)) .ToArray(); int firstNonFluentAssertionsStackFrameIndex = Array.FindIndex( @@ -132,29 +130,29 @@ internal static bool OnlyOneFluentAssertionScopeOnCallStack() int startOfSecondFluentAssertionsScopeStackFrameIndex = Array.FindIndex( allStackFrames, startIndex: firstNonFluentAssertionsStackFrameIndex + 1, - IsCurrentAssembly); + frame => IsCurrentAssembly(frame)); return startOfSecondFluentAssertionsScopeStackFrameIndex < 0; } private static bool IsCustomAssertion(StackFrame frame) { - return frame.GetMethod().IsDecoratedWithOrInherit(); + return frame.GetMethod()?.IsDecoratedWithOrInherit() == true; } private static bool IsDynamic(StackFrame frame) { - return frame.GetMethod().DeclaringType is null; + return frame.GetMethod() is { DeclaringType: null }; } private static bool IsCurrentAssembly(StackFrame frame) { - return frame.GetMethod().DeclaringType?.Assembly == typeof(CallerIdentifier).Assembly; + return frame.GetMethod()?.DeclaringType?.Assembly == typeof(CallerIdentifier).Assembly; } private static bool IsDotNet(StackFrame frame) { - var frameNamespace = frame.GetMethod().DeclaringType.Namespace; + var frameNamespace = frame.GetMethod()?.DeclaringType?.Namespace; var comparisonType = StringComparison.OrdinalIgnoreCase; return frameNamespace?.StartsWith("system.", comparisonType) == true || @@ -163,7 +161,7 @@ private static bool IsDotNet(StackFrame frame) private static bool IsCompilerServices(StackFrame frame) { - return frame.GetMethod().DeclaringType?.Namespace == "System.Runtime.CompilerServices"; + return frame.GetMethod()?.DeclaringType?.Namespace == "System.Runtime.CompilerServices"; } private static string ExtractVariableNameFrom(StackFrame frame) diff --git a/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs b/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs index ff75c7090d..54a10c802c 100644 --- a/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs @@ -946,7 +946,7 @@ public AndConstraint ContainInOrder(IEnumerable expected, string } /// - /// Asserts that the current collection only contains items that are assignable to the type . + /// Asserts that the current collection contains at least one element that is assignable to the type . /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -957,29 +957,18 @@ public AndConstraint ContainInOrder(IEnumerable expected, string /// public AndConstraint ContainItemsAssignableTo(string because = "", params object[] becauseArgs) { - bool success = Execute.Assertion + Execute.Assertion .BecauseOf(because, becauseArgs) + .WithExpectation("Expected {context:collection} to contain at least one element assignable to type {0}{reason}, ", + typeof(TExpectation).FullName) .ForCondition(Subject is not null) - .FailWith("Expected {context:collection} to contain element assignable to type {0}{reason}, but found .", - typeof(TExpectation)); - - if (success) - { - int index = 0; - foreach (T item in Subject) - { - if (item is not TExpectation) - { - Execute.Assertion - .BecauseOf(because, becauseArgs) - .FailWith( - "Expected {context:collection} to contain only items of type {0}{reason}" + - ", but item {1} at index {2} is of type {3}.", typeof(TExpectation), item, index, item.GetType()); - } - - ++index; - } - } + .FailWith("but found .") + .Then + .Given(() => Subject.ConvertOrCastToCollection()) + .ForCondition(subject => subject.Any(x => typeof(TExpectation).IsAssignableFrom(GetType(x)))) + .FailWith("but found {0}.", subject => subject.Select(x => GetType(x))) + .Then + .ClearExpectation(); return new AndConstraint((TAssertions)this); } diff --git a/Src/FluentAssertions/Common/Configuration.cs b/Src/FluentAssertions/Common/Configuration.cs index a59e40be46..d46724dd7e 100644 --- a/Src/FluentAssertions/Common/Configuration.cs +++ b/Src/FluentAssertions/Common/Configuration.cs @@ -76,7 +76,7 @@ private ValueFormatterDetectionMode DetermineFormatterDetectionMode() /// /// Gets or sets the assembly name to scan for custom value formatters in case - /// is set to . + /// is set to . /// public string ValueFormatterAssembly { diff --git a/Src/FluentAssertions/Common/DictionaryHelpers.cs b/Src/FluentAssertions/Common/DictionaryHelpers.cs index 99b3e92d6d..a152c63b00 100644 --- a/Src/FluentAssertions/Common/DictionaryHelpers.cs +++ b/Src/FluentAssertions/Common/DictionaryHelpers.cs @@ -58,9 +58,17 @@ public static bool TryGetValue(this TCollection colle static bool TryGetValue(TCollection collection, TKey key, out TValue value) { Func areSameOrEqual = ObjectExtensions.GetComparer(); - KeyValuePair matchingPair = collection.FirstOrDefault(kvp => areSameOrEqual(kvp.Key, key)); - value = matchingPair.Value; - return matchingPair.Equals(default(KeyValuePair)); + foreach (var kvp in collection) + { + if (areSameOrEqual(kvp.Key, key)) + { + value = kvp.Value; + return true; + } + } + + value = default; + return false; } } diff --git a/Src/FluentAssertions/Data/DataEquivalencyAssertionOptions.cs b/Src/FluentAssertions/Data/DataEquivalencyAssertionOptions.cs index e73cebbba3..e69b9c6597 100644 --- a/Src/FluentAssertions/Data/DataEquivalencyAssertionOptions.cs +++ b/Src/FluentAssertions/Data/DataEquivalencyAssertionOptions.cs @@ -177,7 +177,7 @@ private static MemberInfo GetMemberAccessTargetMember(Expression expression) && (unaryExpression.NodeType == ExpressionType.Convert)) { // If the expression is a value type, then accessing it will involve an - // implicit boxing convertion to type object that we need to ignore. + // implicit boxing conversion to type object that we need to ignore. expression = unaryExpression.Operand; } diff --git a/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs b/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs index 62da346822..8a32b13ea3 100644 --- a/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs +++ b/Src/FluentAssertions/Equivalency/Selection/SelectMemberByPathSelectionRule.cs @@ -22,7 +22,7 @@ public IEnumerable SelectMembers(INode currentNode, IEnumerable utcNow) public T Subject => (T)subject.Target; + private readonly ThreadSafeSequenceGenerator threadSafeSequenceGenerator = new(); + public EventMetadata[] MonitoredEvents { get @@ -49,7 +51,7 @@ public OccurredEvent[] OccurredEvents from eventName in recorderMap.Keys let recording = GetRecordingFor(eventName) from @event in recording - orderby @event.TimestampUtc + orderby @event.Sequence select @event; return query.ToArray(); @@ -125,7 +127,7 @@ private void AttachEventHandler(EventInfo eventInfo, Func utcNow) { if (!recorderMap.TryGetValue(eventInfo.Name, out _)) { - var recorder = new EventRecorder(subject.Target, eventInfo.Name, utcNow); + var recorder = new EventRecorder(subject.Target, eventInfo.Name, utcNow, threadSafeSequenceGenerator); if (recorderMap.TryAdd(eventInfo.Name, recorder)) { recorder.Attach(subject, eventInfo); diff --git a/Src/FluentAssertions/Events/EventRecorder.cs b/Src/FluentAssertions/Events/EventRecorder.cs index 647e216f9e..abea8d3b2b 100644 --- a/Src/FluentAssertions/Events/EventRecorder.cs +++ b/Src/FluentAssertions/Events/EventRecorder.cs @@ -25,11 +25,13 @@ internal sealed class EventRecorder : IEventRecording, IDisposable /// The object events are recorded from /// The name of the event that's recorded /// A delegate to get the current date and time in UTC format. - public EventRecorder(object eventRaiser, string eventName, Func utcNow) + /// Class used to generate a sequence in a thread-safe manner. + public EventRecorder(object eventRaiser, string eventName, Func utcNow, ThreadSafeSequenceGenerator sequenceGenerator) { this.utcNow = utcNow; EventObject = eventRaiser; EventName = eventName; + this.sequenceGenerator = sequenceGenerator; } /// @@ -40,6 +42,8 @@ public EventRecorder(object eventRaiser, string eventName, Func utcNow /// public string EventName { get; } + private readonly ThreadSafeSequenceGenerator sequenceGenerator; + public Type EventHandlerType { get; private set; } public void Attach(WeakReference subject, EventInfo eventInfo) @@ -77,7 +81,7 @@ public void RecordEvent(params object[] parameters) { lock (lockable) { - raisedEvents.Add(new RecordedEvent(utcNow(), parameters)); + raisedEvents.Add(new RecordedEvent(utcNow(), sequenceGenerator.Increment(), parameters)); } } @@ -105,7 +109,8 @@ public IEnumerator GetEnumerator() { EventName = EventName, Parameters = @event.Parameters, - TimestampUtc = @event.TimestampUtc + TimestampUtc = @event.TimestampUtc, + Sequence = @event.Sequence }; } } diff --git a/Src/FluentAssertions/Events/OccurredEvent.cs b/Src/FluentAssertions/Events/OccurredEvent.cs index 937025bfd3..761341ea29 100644 --- a/Src/FluentAssertions/Events/OccurredEvent.cs +++ b/Src/FluentAssertions/Events/OccurredEvent.cs @@ -21,5 +21,10 @@ public class OccurredEvent /// The exact date and time of the occurrence in . /// public DateTime TimestampUtc { get; set; } + + /// + /// The order in which this event was raised on the monitored object. + /// + public int Sequence { get; set; } } } diff --git a/Src/FluentAssertions/Events/RecordedEvent.cs b/Src/FluentAssertions/Events/RecordedEvent.cs index 1930a605ee..69168a7692 100644 --- a/Src/FluentAssertions/Events/RecordedEvent.cs +++ b/Src/FluentAssertions/Events/RecordedEvent.cs @@ -12,10 +12,11 @@ internal class RecordedEvent /// /// Default constructor stores the parameters the event was raised with /// - public RecordedEvent(DateTime utcNow, params object[] parameters) + public RecordedEvent(DateTime utcNow, int sequence, params object[] parameters) { Parameters = parameters; TimestampUtc = utcNow; + Sequence = sequence; } /// @@ -27,5 +28,10 @@ public RecordedEvent(DateTime utcNow, params object[] parameters) /// Parameters for the event /// public object[] Parameters { get; } + + /// + /// The order in which this event was invoked on the monitored object. + /// + public int Sequence { get; } } } diff --git a/Src/FluentAssertions/Events/ThreadSafeSequenceGenerator.cs b/Src/FluentAssertions/Events/ThreadSafeSequenceGenerator.cs new file mode 100644 index 0000000000..4ccf4ad443 --- /dev/null +++ b/Src/FluentAssertions/Events/ThreadSafeSequenceGenerator.cs @@ -0,0 +1,20 @@ +using System.Threading; + +namespace FluentAssertions.Events +{ + /// + /// Generates a sequence in a thread-safe manner. + /// + internal sealed class ThreadSafeSequenceGenerator + { + private int sequence = -1; + + /// + /// Increments the current sequence. + /// + public int Increment() + { + return Interlocked.Increment(ref sequence); + } + } +} diff --git a/Src/FluentAssertions/ExceptionAssertionsExtensions.cs b/Src/FluentAssertions/ExceptionAssertionsExtensions.cs index 13376a3a46..5572cebbc3 100644 --- a/Src/FluentAssertions/ExceptionAssertionsExtensions.cs +++ b/Src/FluentAssertions/ExceptionAssertionsExtensions.cs @@ -80,6 +80,28 @@ public static async Task> WithInnerExceptio return (await task).WithInnerException(because, becauseArgs); } + /// + /// Asserts that the thrown exception contains an inner exception of type . + /// + /// The expected type of the exception. + /// The containing the thrown exception. + /// + /// 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 static async Task> WithInnerException( + this Task> task, + Type innerException, + string because = "", + params object[] becauseArgs) + where TException : Exception + { + return (await task).WithInnerException(innerException, because, becauseArgs); + } + /// /// Asserts that the thrown exception contains an inner exception of the exact type (and not a derived exception type). /// @@ -103,6 +125,28 @@ public static async Task> WithInnerExceptio return (await task).WithInnerExceptionExactly(because, becauseArgs); } + /// + /// Asserts that the thrown exception contains an inner exception of the exact type (and not a derived exception type). + /// + /// The expected type of the exception. + /// The containing the thrown exception. + /// + /// 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 static async Task> WithInnerExceptionExactly( + this Task> task, + Type innerException, + string because = "", + params object[] becauseArgs) + where TException : Exception + { + return (await task).WithInnerExceptionExactly(innerException, because, becauseArgs); + } + /// /// Asserts that the thrown exception has a parameter which name matches . /// diff --git a/Src/FluentAssertions/Execution/GivenSelector.cs b/Src/FluentAssertions/Execution/GivenSelector.cs index 395e2aeb12..58d46f2647 100644 --- a/Src/FluentAssertions/Execution/GivenSelector.cs +++ b/Src/FluentAssertions/Execution/GivenSelector.cs @@ -49,7 +49,7 @@ public GivenSelector ForCondition(Func predicate) /// The will not be invoked if the prior assertion failed, /// nor will throw any exceptions. /// - /// + /// public GivenSelector Given(Func selector) { Guard.ThrowIfArgumentIsNull(selector, nameof(selector)); diff --git a/Src/FluentAssertions/Execution/IAssertionScope.cs b/Src/FluentAssertions/Execution/IAssertionScope.cs index e59099e9c4..a70a57c678 100644 --- a/Src/FluentAssertions/Execution/IAssertionScope.cs +++ b/Src/FluentAssertions/Execution/IAssertionScope.cs @@ -44,7 +44,7 @@ public interface IAssertionScope : IDisposable Continuation FailWith(string message); /// - /// Sets the failure message when the assertion is not met, or completes the failure message set to aprior call to + /// Sets the failure message when the assertion is not met, or completes the failure message set to a prior call to /// . /// will not be called unless the assertion is not met. /// diff --git a/Src/FluentAssertions/Execution/MessageBuilder.cs b/Src/FluentAssertions/Execution/MessageBuilder.cs index c580c5bd34..d2c3a60471 100644 --- a/Src/FluentAssertions/Execution/MessageBuilder.cs +++ b/Src/FluentAssertions/Execution/MessageBuilder.cs @@ -91,7 +91,14 @@ private string FormatArgumentPlaceholders(string failureMessage, object[] failur { string[] values = failureArgs.Select(a => Formatter.ToString(a, formattingOptions)).ToArray(); - return string.Format(CultureInfo.InvariantCulture, failureMessage, values); + try + { + return string.Format(CultureInfo.InvariantCulture, failureMessage, values); + } + catch (FormatException formatException) + { + return $"**WARNING** failure message '{failureMessage}' could not be formatted with string.Format{Environment.NewLine}{formatException.StackTrace}"; + } } private string SanitizeReason(string reason) diff --git a/Src/FluentAssertions/FluentAssertions.csproj b/Src/FluentAssertions/FluentAssertions.csproj index 2204810ff1..1a3d1f3de8 100644 --- a/Src/FluentAssertions/FluentAssertions.csproj +++ b/Src/FluentAssertions/FluentAssertions.csproj @@ -1,4 +1,5 @@ + net47;netcoreapp2.1;netcoreapp3.0;netstandard2.0;netstandard2.1 True @@ -11,6 +12,7 @@ ..\..\Rules.ruleset $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb true + true Dennis Doomen;Jonas Nyrup @@ -29,6 +31,11 @@ Copyright Dennis Doomen 2010-2020 9.0 + + false + false + false + <_Parameter1>FluentAssertions.Specs, PublicKey=00240000048000009400000006020000002400005253413100040000010001002d25ff515c85b13ba08f61d466cff5d80a7f28ba197bbf8796085213e7a3406f970d2a4874932fed35db546e89af2da88c194bf1b7f7ac70de7988c78406f7629c547283061282a825616eb7eb48a9514a7570942936020a9bb37dca9ff60b778309900851575614491c6d25018fadb75828f4c7a17bf2d7dc86e7b6eafc5d8f @@ -75,6 +82,7 @@ + diff --git a/Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs b/Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs new file mode 100644 index 0000000000..3b3e96779f --- /dev/null +++ b/Src/FluentAssertions/Primitives/HttpResponseMessageAssertions.cs @@ -0,0 +1,236 @@ +using System.Diagnostics; +using System.Net; +using System.Net.Http; +using FluentAssertions.Execution; + +namespace FluentAssertions.Primitives +{ + /// + /// Contains a number of methods to assert that a is in the expected state. + /// + [DebuggerNonUserCode] + public class HttpResponseMessageAssertions : HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(HttpResponseMessage value) + : base(value) + { + } + } + + /// + /// Contains a number of methods to assert that a is in the expected state. + /// + [DebuggerNonUserCode] + public class HttpResponseMessageAssertions : ObjectAssertions + where TAssertions : HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(HttpResponseMessage value) + : base(value) + { + } + + /// + /// Asserts that the is successful (2xx). + /// + /// + /// 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 AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be successful (2xx){reason}, but HttpResponseMessage was ."); + + if (success) + { + Execute.Assertion + .ForCondition(Subject!.IsSuccessStatusCode) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be successful (2xx){reason}, but found {0}.", Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is redirection (3xx). + /// + /// + /// 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 AndConstraint BeRedirection(string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be redirection (3xx){reason}, but HttpResponseMessage was ."); + + if (success) + { + Execute.Assertion + .ForCondition((int)Subject!.StatusCode is >= 300 and <= 399) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be redirection (3xx){reason}, but found {0}.", Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is either client (4xx) or server error (5xx). + /// + /// + /// 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 AndConstraint HaveError(string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be an error{reason}, but HttpResponseMessage was ."); + + if (success) + { + Execute.Assertion + .ForCondition(IsClientError() || IsServerError()) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be an error{reason}, but found {0}.", Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is client error (4xx). + /// + /// + /// 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 AndConstraint HaveClientError(string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be client error (4xx){reason}, but HttpResponseMessage was ."); + + if (success) + { + Execute.Assertion + .ForCondition(IsClientError()) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be client error (4xx){reason}, but found {0}.", Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is server error (5xx). + /// + /// + /// 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 AndConstraint HaveServerError(string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be server error (5xx){reason}, but HttpResponseMessage was ."); + + if (success) + { + Execute.Assertion + .ForCondition(IsServerError()) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be server error (5xx){reason}, but found {0}.", Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is equal to the specified 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 AndConstraint HaveStatusCode(HttpStatusCode expected, string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be {0}{reason}, but HttpResponseMessage was .", expected); + + if (success) + { + Execute.Assertion + .ForCondition(Subject!.StatusCode == expected) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode to be {0}{reason}, but found {1}.", expected, Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + /// + /// Asserts that the is not equal to the specified value. + /// + /// The unexpected 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 AndConstraint NotHaveStatusCode(HttpStatusCode unexpected, string because = "", params object[] becauseArgs) + { + var success = Execute.Assertion + .ForCondition(Subject is not null) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode not to be {0}{reason}, but HttpResponseMessage was .", unexpected); + + if (success) + { + Execute.Assertion + .ForCondition(Subject!.StatusCode != unexpected) + .BecauseOf(because, becauseArgs) + .FailWith("Expected HttpStatusCode not to be {0}{reason}, but found {1}.", unexpected, Subject.StatusCode); + } + + return new AndConstraint((TAssertions)this); + } + + private bool IsServerError() => (int)Subject.StatusCode is >= 500 and <= 599; + + private bool IsClientError() => (int)Subject.StatusCode is >= 400 and <= 499; + + protected override string Identifier => "HTTP response message"; + } +} diff --git a/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs b/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs index b3a56a1e56..80e53e1490 100644 --- a/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs +++ b/Src/FluentAssertions/Primitives/ReferenceTypeAssertions.cs @@ -416,6 +416,7 @@ public AndConstraint Match(Expression> predicate, /// /// Returns the type of the subject the assertion applies on. + /// It should be a user-friendly name as it is included in the failure message. /// protected abstract string Identifier { get; } diff --git a/Src/FluentAssertions/Specialized/ExceptionAssertions.cs b/Src/FluentAssertions/Specialized/ExceptionAssertions.cs index cb559a624b..f5b735193a 100644 --- a/Src/FluentAssertions/Specialized/ExceptionAssertions.cs +++ b/Src/FluentAssertions/Specialized/ExceptionAssertions.cs @@ -3,9 +3,7 @@ using System.Diagnostics; using System.Linq; using System.Linq.Expressions; - using FluentAssertions.Common; -using FluentAssertions.Equivalency; using FluentAssertions.Equivalency.Steps; using FluentAssertions.Execution; using FluentAssertions.Formatting; @@ -89,7 +87,8 @@ public virtual ExceptionAssertions WithMessage(string expectedWildca .ForCondition(Subject.Any()) .FailWith("Expected exception with message {0}{reason}, but no exception was thrown.", expectedWildcardPattern); - OuterMessageAssertion.Execute(Subject.Select(exc => exc.Message).ToArray(), expectedWildcardPattern, because, becauseArgs); + OuterMessageAssertion.Execute(Subject.Select(exc => exc.Message).ToArray(), expectedWildcardPattern, because, + becauseArgs); return this; } @@ -109,28 +108,24 @@ public virtual ExceptionAssertions WithInnerException e.InnerException is not null)) - .FailWith("the thrown exception has no inner exception.") - .Then - .ClearExpectation(); - - TInnerException[] expectedInnerExceptions = Subject - .Select(e => e.InnerException) - .OfType() - .ToArray(); - - Execute.Assertion - .ForCondition(expectedInnerExceptions.Any()) - .BecauseOf(because, becauseArgs) - .FailWith("Expected inner {0}{reason}, but found {1}.", typeof(TInnerException), SingleSubject.InnerException); + var expectedInnerExceptions = AssertInnerExceptions(typeof(TInnerException), because, becauseArgs); + return new ExceptionAssertions(expectedInnerExceptions.Cast()); + } - return new ExceptionAssertions(expectedInnerExceptions); + /// + /// Asserts that the thrown exception contains an inner exception of type . + /// + /// + /// 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 ExceptionAssertions WithInnerException(Type innerException, string because = null, + params object[] becauseArgs) + { + return new ExceptionAssertions(AssertInnerExceptions(innerException, because, becauseArgs)); } /// @@ -148,19 +143,24 @@ public virtual ExceptionAssertions WithInnerExceptionExactly(because, becauseArgs); - - TInnerException[] expectedExceptions = Subject - .Select(e => e.InnerException) - .OfType() - .Where(e => e?.GetType() == typeof(TInnerException)).ToArray(); - - Execute.Assertion - .ForCondition(expectedExceptions.Any()) - .BecauseOf(because, becauseArgs) - .FailWith("Expected inner {0}{reason}, but found {1}.", typeof(TInnerException), SingleSubject.InnerException); + var exceptionExpression = AssertInnerExceptionExactly(typeof(TInnerException), because, becauseArgs); + return new ExceptionAssertions(exceptionExpression.Cast()); + } - return new ExceptionAssertions(expectedExceptions); + /// + /// Asserts that the thrown exception contains an inner exception of the exact type (and not a derived exception type). + /// + /// + /// 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 ExceptionAssertions WithInnerExceptionExactly(Type innerException, string because = null, + params object[] becauseArgs) + { + return new ExceptionAssertions(AssertInnerExceptionExactly(innerException, because, becauseArgs)); } /// @@ -191,6 +191,54 @@ public ExceptionAssertions Where(Expression> return this; } + private IEnumerable AssertInnerExceptionExactly(Type innerException, string because = null, + params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(innerException, nameof(innerException)); + + AssertInnerExceptions(innerException, because, becauseArgs); + + Exception[] expectedExceptions = Subject + .Select(e => e.InnerException) + .Where(e => e?.GetType() == innerException).ToArray(); + + Execute.Assertion + .ForCondition(expectedExceptions.Any()) + .BecauseOf(because, becauseArgs) + .FailWith("Expected inner {0}{reason}, but found {1}.", innerException, SingleSubject.InnerException); + + return expectedExceptions; + } + + private IEnumerable AssertInnerExceptions(Type innerException, string because = null, + params object[] becauseArgs) + { + Guard.ThrowIfArgumentIsNull(innerException, nameof(innerException)); + + Execute.Assertion + .BecauseOf(because, becauseArgs) + .WithExpectation("Expected inner {0}{reason}, but ", innerException) + .ForCondition(Subject is not null) + .FailWith("no exception was thrown.") + .Then + .ForCondition(Subject.Any(e => e.InnerException is not null)) + .FailWith("the thrown exception has no inner exception.") + .Then + .ClearExpectation(); + + Exception[] expectedInnerExceptions = Subject + .Select(e => e.InnerException) + .Where(e => e != null && e.GetType().IsSameOrInherits(innerException)) + .ToArray(); + + Execute.Assertion + .ForCondition(expectedInnerExceptions.Any()) + .BecauseOf(because, becauseArgs) + .FailWith("Expected inner {0}{reason}, but found {1}.", innerException, SingleSubject.InnerException); + + return expectedInnerExceptions; + } + private TException SingleSubject { get diff --git a/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs b/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs index 018e873a61..8b193ba680 100644 --- a/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs +++ b/Src/FluentAssertions/Streams/BufferedStreamAssertions.cs @@ -27,6 +27,8 @@ public BufferedStreamAssertions(BufferedStream stream) { } + protected override string Identifier => "buffered stream"; + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1 /// /// Asserts that the current has the buffer size. diff --git a/Src/FluentAssertions/Types/MethodBaseAssertions.cs b/Src/FluentAssertions/Types/MethodBaseAssertions.cs index ddf91bef42..2136b6a769 100644 --- a/Src/FluentAssertions/Types/MethodBaseAssertions.cs +++ b/Src/FluentAssertions/Types/MethodBaseAssertions.cs @@ -93,8 +93,6 @@ public AndConstraint NotHaveAccessModifier(CSharpAccessModifier acc return new AndConstraint((TAssertions)this); } - protected override string Identifier => "methodBase"; - internal static string GetParameterString(MethodBase methodBase) { IEnumerable parameterTypes = methodBase.GetParameters().Select(p => p.ParameterType); diff --git a/Src/FluentAssertions/Types/MethodInfoSelector.cs b/Src/FluentAssertions/Types/MethodInfoSelector.cs index e3ba8ca102..f800d0d656 100644 --- a/Src/FluentAssertions/Types/MethodInfoSelector.cs +++ b/Src/FluentAssertions/Types/MethodInfoSelector.cs @@ -35,7 +35,7 @@ public MethodInfoSelector(IEnumerable types) Guard.ThrowIfArgumentContainsNull(types, nameof(types)); selectedMethods = types.SelectMany(t => t - .GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) .Where(method => !HasSpecialName(method))); } @@ -151,6 +151,24 @@ public MethodInfoSelector ThatAreNotAsync() return this; } + /// + /// Only return methods that are static. + /// + public MethodInfoSelector ThatAreStatic() + { + selectedMethods = selectedMethods.Where(method => method.IsStatic); + return this; + } + + /// + /// Only return methods that are not static. + /// + public MethodInfoSelector ThatAreNotStatic() + { + selectedMethods = selectedMethods.Where(method => !method.IsStatic); + return this; + } + /// /// Only return methods that are virtual. /// diff --git a/Src/FluentAssertions/Xml/XmlElementAssertions.cs b/Src/FluentAssertions/Xml/XmlElementAssertions.cs index 11283d19be..46814dc5fe 100644 --- a/Src/FluentAssertions/Xml/XmlElementAssertions.cs +++ b/Src/FluentAssertions/Xml/XmlElementAssertions.cs @@ -165,5 +165,7 @@ public AndWhichConstraint HaveElementWithNames return new AndWhichConstraint(this, element); } + + protected override string Identifier => "XML element"; } } diff --git a/Src/FluentAssertions/Xml/XmlNodeAssertions.cs b/Src/FluentAssertions/Xml/XmlNodeAssertions.cs index 6abe32d325..a8d571f05d 100644 --- a/Src/FluentAssertions/Xml/XmlNodeAssertions.cs +++ b/Src/FluentAssertions/Xml/XmlNodeAssertions.cs @@ -80,6 +80,6 @@ public AndConstraint NotBeEquivalentTo(XmlNode unexpected, string b /// /// Returns the type of the subject the assertion applies on. /// - protected override string Identifier => "Xml Node"; + protected override string Identifier => "XML node"; } } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt index ee73949aa0..64e793a717 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt @@ -66,6 +66,7 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should(this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } + public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } @@ -241,9 +242,13 @@ namespace FluentAssertions { public static System.Threading.Tasks.Task> Where(this System.Threading.Tasks.Task> task, System.Linq.Expressions.Expression> exceptionExpression, string because = "", params object[] becauseArgs) where TException : System.Exception { } + public static System.Threading.Tasks.Task> WithInnerException(this System.Threading.Tasks.Task> task, System.Type innerException, string because = "", params object[] becauseArgs) + where TException : System.Exception { } public static System.Threading.Tasks.Task> WithInnerException(this System.Threading.Tasks.Task> task, string because = "", params object[] becauseArgs) where TException : System.Exception where TInnerException : System.Exception { } + public static System.Threading.Tasks.Task> WithInnerExceptionExactly(this System.Threading.Tasks.Task> task, System.Type innerException, string because = "", params object[] becauseArgs) + where TException : System.Exception { } public static System.Threading.Tasks.Task> WithInnerExceptionExactly(this System.Threading.Tasks.Task> task, string because = "", params object[] becauseArgs) where TException : System.Exception where TInnerException : System.Exception { } @@ -1216,6 +1221,7 @@ namespace FluentAssertions.Events public OccurredEvent() { } public string EventName { get; set; } public object[] Parameters { get; set; } + public int Sequence { get; set; } public System.DateTime TimestampUtc { get; set; } } } @@ -1909,6 +1915,23 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions + where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + protected override string Identifier { get; } + public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } + } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { public NullableBooleanAssertions(bool? value) { } @@ -2192,8 +2215,10 @@ namespace FluentAssertions.Specialized protected override string Identifier { get; } public TException Which { get; } public FluentAssertions.Specialized.ExceptionAssertions Where(System.Linq.Expressions.Expression> exceptionExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.Specialized.ExceptionAssertions WithInnerException(System.Type innerException, string because = null, params object[] becauseArgs) { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithInnerException(string because = null, params object[] becauseArgs) where TInnerException : System.Exception { } + public FluentAssertions.Specialized.ExceptionAssertions WithInnerExceptionExactly(System.Type innerException, string because = null, params object[] becauseArgs) { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithInnerExceptionExactly(string because = null, params object[] becauseArgs) where TInnerException : System.Exception { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithMessage(string expectedWildcardPattern, string because = "", params object[] becauseArgs) { } @@ -2267,6 +2292,7 @@ namespace FluentAssertions.Streams where TAssertions : FluentAssertions.Streams.BufferedStreamAssertions { public BufferedStreamAssertions(System.IO.BufferedStream stream) { } + protected override string Identifier { get; } } public class StreamAssertions : FluentAssertions.Streams.StreamAssertions { @@ -2325,7 +2351,6 @@ namespace FluentAssertions.Types where TAssertions : FluentAssertions.Types.MethodBaseAssertions { protected MethodBaseAssertions(TSubject subject) { } - protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } } @@ -2363,7 +2388,9 @@ namespace FluentAssertions.Types where TAttribute : System.Attribute { } public FluentAssertions.Types.MethodInfoSelector ThatAreNotDecoratedWithOrInherit() where TAttribute : System.Attribute { } + public FluentAssertions.Types.MethodInfoSelector ThatAreNotStatic() { } public FluentAssertions.Types.MethodInfoSelector ThatAreNotVirtual() { } + public FluentAssertions.Types.MethodInfoSelector ThatAreStatic() { } public FluentAssertions.Types.MethodInfoSelector ThatAreVirtual() { } public FluentAssertions.Types.MethodInfoSelector ThatDoNotReturn() { } public FluentAssertions.Types.MethodInfoSelector ThatReturn() { } @@ -2620,6 +2647,7 @@ namespace FluentAssertions.Xml public class XmlElementAssertions : FluentAssertions.Xml.XmlNodeAssertions { public XmlElementAssertions(System.Xml.XmlElement xmlElement) { } + protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveAttributeWithNamespace(string expectedName, string expectedNamespace, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(string expectedName, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt index d5b927c7d6..78fbc1dd87 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp2.1.verified.txt @@ -66,6 +66,7 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should(this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } + public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } @@ -241,9 +242,13 @@ namespace FluentAssertions { public static System.Threading.Tasks.Task> Where(this System.Threading.Tasks.Task> task, System.Linq.Expressions.Expression> exceptionExpression, string because = "", params object[] becauseArgs) where TException : System.Exception { } + public static System.Threading.Tasks.Task> WithInnerException(this System.Threading.Tasks.Task> task, System.Type innerException, string because = "", params object[] becauseArgs) + where TException : System.Exception { } public static System.Threading.Tasks.Task> WithInnerException(this System.Threading.Tasks.Task> task, string because = "", params object[] becauseArgs) where TException : System.Exception where TInnerException : System.Exception { } + public static System.Threading.Tasks.Task> WithInnerExceptionExactly(this System.Threading.Tasks.Task> task, System.Type innerException, string because = "", params object[] becauseArgs) + where TException : System.Exception { } public static System.Threading.Tasks.Task> WithInnerExceptionExactly(this System.Threading.Tasks.Task> task, string because = "", params object[] becauseArgs) where TException : System.Exception where TInnerException : System.Exception { } @@ -1216,6 +1221,7 @@ namespace FluentAssertions.Events public OccurredEvent() { } public string EventName { get; set; } public object[] Parameters { get; set; } + public int Sequence { get; set; } public System.DateTime TimestampUtc { get; set; } } } @@ -1909,6 +1915,23 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions + where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + protected override string Identifier { get; } + public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } + } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { public NullableBooleanAssertions(bool? value) { } @@ -2192,8 +2215,10 @@ namespace FluentAssertions.Specialized protected override string Identifier { get; } public TException Which { get; } public FluentAssertions.Specialized.ExceptionAssertions Where(System.Linq.Expressions.Expression> exceptionExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.Specialized.ExceptionAssertions WithInnerException(System.Type innerException, string because = null, params object[] becauseArgs) { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithInnerException(string because = null, params object[] becauseArgs) where TInnerException : System.Exception { } + public FluentAssertions.Specialized.ExceptionAssertions WithInnerExceptionExactly(System.Type innerException, string because = null, params object[] becauseArgs) { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithInnerExceptionExactly(string because = null, params object[] becauseArgs) where TInnerException : System.Exception { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithMessage(string expectedWildcardPattern, string because = "", params object[] becauseArgs) { } @@ -2267,6 +2292,7 @@ namespace FluentAssertions.Streams where TAssertions : FluentAssertions.Streams.BufferedStreamAssertions { public BufferedStreamAssertions(System.IO.BufferedStream stream) { } + protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveBufferSize(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveBufferSize(int unexpected, string because = "", params object[] becauseArgs) { } } @@ -2327,7 +2353,6 @@ namespace FluentAssertions.Types where TAssertions : FluentAssertions.Types.MethodBaseAssertions { protected MethodBaseAssertions(TSubject subject) { } - protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } } @@ -2365,7 +2390,9 @@ namespace FluentAssertions.Types where TAttribute : System.Attribute { } public FluentAssertions.Types.MethodInfoSelector ThatAreNotDecoratedWithOrInherit() where TAttribute : System.Attribute { } + public FluentAssertions.Types.MethodInfoSelector ThatAreNotStatic() { } public FluentAssertions.Types.MethodInfoSelector ThatAreNotVirtual() { } + public FluentAssertions.Types.MethodInfoSelector ThatAreStatic() { } public FluentAssertions.Types.MethodInfoSelector ThatAreVirtual() { } public FluentAssertions.Types.MethodInfoSelector ThatDoNotReturn() { } public FluentAssertions.Types.MethodInfoSelector ThatReturn() { } @@ -2622,6 +2649,7 @@ namespace FluentAssertions.Xml public class XmlElementAssertions : FluentAssertions.Xml.XmlNodeAssertions { public XmlElementAssertions(System.Xml.XmlElement xmlElement) { } + protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveAttributeWithNamespace(string expectedName, string expectedNamespace, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(string expectedName, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt index 62d7b60d9a..a71b144a73 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netcoreapp3.0.verified.txt @@ -66,6 +66,7 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should(this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } + public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } @@ -241,9 +242,13 @@ namespace FluentAssertions { public static System.Threading.Tasks.Task> Where(this System.Threading.Tasks.Task> task, System.Linq.Expressions.Expression> exceptionExpression, string because = "", params object[] becauseArgs) where TException : System.Exception { } + public static System.Threading.Tasks.Task> WithInnerException(this System.Threading.Tasks.Task> task, System.Type innerException, string because = "", params object[] becauseArgs) + where TException : System.Exception { } public static System.Threading.Tasks.Task> WithInnerException(this System.Threading.Tasks.Task> task, string because = "", params object[] becauseArgs) where TException : System.Exception where TInnerException : System.Exception { } + public static System.Threading.Tasks.Task> WithInnerExceptionExactly(this System.Threading.Tasks.Task> task, System.Type innerException, string because = "", params object[] becauseArgs) + where TException : System.Exception { } public static System.Threading.Tasks.Task> WithInnerExceptionExactly(this System.Threading.Tasks.Task> task, string because = "", params object[] becauseArgs) where TException : System.Exception where TInnerException : System.Exception { } @@ -1216,6 +1221,7 @@ namespace FluentAssertions.Events public OccurredEvent() { } public string EventName { get; set; } public object[] Parameters { get; set; } + public int Sequence { get; set; } public System.DateTime TimestampUtc { get; set; } } } @@ -1909,6 +1915,23 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions + where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + protected override string Identifier { get; } + public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } + } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { public NullableBooleanAssertions(bool? value) { } @@ -2192,8 +2215,10 @@ namespace FluentAssertions.Specialized protected override string Identifier { get; } public TException Which { get; } public FluentAssertions.Specialized.ExceptionAssertions Where(System.Linq.Expressions.Expression> exceptionExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.Specialized.ExceptionAssertions WithInnerException(System.Type innerException, string because = null, params object[] becauseArgs) { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithInnerException(string because = null, params object[] becauseArgs) where TInnerException : System.Exception { } + public FluentAssertions.Specialized.ExceptionAssertions WithInnerExceptionExactly(System.Type innerException, string because = null, params object[] becauseArgs) { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithInnerExceptionExactly(string because = null, params object[] becauseArgs) where TInnerException : System.Exception { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithMessage(string expectedWildcardPattern, string because = "", params object[] becauseArgs) { } @@ -2267,6 +2292,7 @@ namespace FluentAssertions.Streams where TAssertions : FluentAssertions.Streams.BufferedStreamAssertions { public BufferedStreamAssertions(System.IO.BufferedStream stream) { } + protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveBufferSize(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveBufferSize(int unexpected, string because = "", params object[] becauseArgs) { } } @@ -2327,7 +2353,6 @@ namespace FluentAssertions.Types where TAssertions : FluentAssertions.Types.MethodBaseAssertions { protected MethodBaseAssertions(TSubject subject) { } - protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } } @@ -2365,7 +2390,9 @@ namespace FluentAssertions.Types where TAttribute : System.Attribute { } public FluentAssertions.Types.MethodInfoSelector ThatAreNotDecoratedWithOrInherit() where TAttribute : System.Attribute { } + public FluentAssertions.Types.MethodInfoSelector ThatAreNotStatic() { } public FluentAssertions.Types.MethodInfoSelector ThatAreNotVirtual() { } + public FluentAssertions.Types.MethodInfoSelector ThatAreStatic() { } public FluentAssertions.Types.MethodInfoSelector ThatAreVirtual() { } public FluentAssertions.Types.MethodInfoSelector ThatDoNotReturn() { } public FluentAssertions.Types.MethodInfoSelector ThatReturn() { } @@ -2622,6 +2649,7 @@ namespace FluentAssertions.Xml public class XmlElementAssertions : FluentAssertions.Xml.XmlNodeAssertions { public XmlElementAssertions(System.Xml.XmlElement xmlElement) { } + protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveAttributeWithNamespace(string expectedName, string expectedNamespace, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(string expectedName, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt index 45c197d938..da28d3d67b 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt @@ -65,6 +65,7 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should(this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } + public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } @@ -234,9 +235,13 @@ namespace FluentAssertions { public static System.Threading.Tasks.Task> Where(this System.Threading.Tasks.Task> task, System.Linq.Expressions.Expression> exceptionExpression, string because = "", params object[] becauseArgs) where TException : System.Exception { } + public static System.Threading.Tasks.Task> WithInnerException(this System.Threading.Tasks.Task> task, System.Type innerException, string because = "", params object[] becauseArgs) + where TException : System.Exception { } public static System.Threading.Tasks.Task> WithInnerException(this System.Threading.Tasks.Task> task, string because = "", params object[] becauseArgs) where TException : System.Exception where TInnerException : System.Exception { } + public static System.Threading.Tasks.Task> WithInnerExceptionExactly(this System.Threading.Tasks.Task> task, System.Type innerException, string because = "", params object[] becauseArgs) + where TException : System.Exception { } public static System.Threading.Tasks.Task> WithInnerExceptionExactly(this System.Threading.Tasks.Task> task, string because = "", params object[] becauseArgs) where TException : System.Exception where TInnerException : System.Exception { } @@ -1862,6 +1867,23 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions + where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + protected override string Identifier { get; } + public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } + } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { public NullableBooleanAssertions(bool? value) { } @@ -2145,8 +2167,10 @@ namespace FluentAssertions.Specialized protected override string Identifier { get; } public TException Which { get; } public FluentAssertions.Specialized.ExceptionAssertions Where(System.Linq.Expressions.Expression> exceptionExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.Specialized.ExceptionAssertions WithInnerException(System.Type innerException, string because = null, params object[] becauseArgs) { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithInnerException(string because = null, params object[] becauseArgs) where TInnerException : System.Exception { } + public FluentAssertions.Specialized.ExceptionAssertions WithInnerExceptionExactly(System.Type innerException, string because = null, params object[] becauseArgs) { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithInnerExceptionExactly(string because = null, params object[] becauseArgs) where TInnerException : System.Exception { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithMessage(string expectedWildcardPattern, string because = "", params object[] becauseArgs) { } @@ -2220,6 +2244,7 @@ namespace FluentAssertions.Streams where TAssertions : FluentAssertions.Streams.BufferedStreamAssertions { public BufferedStreamAssertions(System.IO.BufferedStream stream) { } + protected override string Identifier { get; } } public class StreamAssertions : FluentAssertions.Streams.StreamAssertions { @@ -2278,7 +2303,6 @@ namespace FluentAssertions.Types where TAssertions : FluentAssertions.Types.MethodBaseAssertions { protected MethodBaseAssertions(TSubject subject) { } - protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } } @@ -2316,7 +2340,9 @@ namespace FluentAssertions.Types where TAttribute : System.Attribute { } public FluentAssertions.Types.MethodInfoSelector ThatAreNotDecoratedWithOrInherit() where TAttribute : System.Attribute { } + public FluentAssertions.Types.MethodInfoSelector ThatAreNotStatic() { } public FluentAssertions.Types.MethodInfoSelector ThatAreNotVirtual() { } + public FluentAssertions.Types.MethodInfoSelector ThatAreStatic() { } public FluentAssertions.Types.MethodInfoSelector ThatAreVirtual() { } public FluentAssertions.Types.MethodInfoSelector ThatDoNotReturn() { } public FluentAssertions.Types.MethodInfoSelector ThatReturn() { } @@ -2573,6 +2599,7 @@ namespace FluentAssertions.Xml public class XmlElementAssertions : FluentAssertions.Xml.XmlNodeAssertions { public XmlElementAssertions(System.Xml.XmlElement xmlElement) { } + protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveAttributeWithNamespace(string expectedName, string expectedNamespace, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(string expectedName, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt index 49ebf2d8c8..a2ecfc1c6f 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt @@ -66,6 +66,7 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableGuidAssertions Should(this System.Guid? actualValue) { } public static FluentAssertions.Streams.BufferedStreamAssertions Should(this System.IO.BufferedStream actualValue) { } public static FluentAssertions.Streams.StreamAssertions Should(this System.IO.Stream actualValue) { } + public static FluentAssertions.Primitives.HttpResponseMessageAssertions Should(this System.Net.Http.HttpResponseMessage actualValue) { } public static FluentAssertions.Reflection.AssemblyAssertions Should(this System.Reflection.Assembly assembly) { } public static FluentAssertions.Types.ConstructorInfoAssertions Should(this System.Reflection.ConstructorInfo constructorInfo) { } public static FluentAssertions.Types.MethodInfoAssertions Should(this System.Reflection.MethodInfo methodInfo) { } @@ -241,9 +242,13 @@ namespace FluentAssertions { public static System.Threading.Tasks.Task> Where(this System.Threading.Tasks.Task> task, System.Linq.Expressions.Expression> exceptionExpression, string because = "", params object[] becauseArgs) where TException : System.Exception { } + public static System.Threading.Tasks.Task> WithInnerException(this System.Threading.Tasks.Task> task, System.Type innerException, string because = "", params object[] becauseArgs) + where TException : System.Exception { } public static System.Threading.Tasks.Task> WithInnerException(this System.Threading.Tasks.Task> task, string because = "", params object[] becauseArgs) where TException : System.Exception where TInnerException : System.Exception { } + public static System.Threading.Tasks.Task> WithInnerExceptionExactly(this System.Threading.Tasks.Task> task, System.Type innerException, string because = "", params object[] becauseArgs) + where TException : System.Exception { } public static System.Threading.Tasks.Task> WithInnerExceptionExactly(this System.Threading.Tasks.Task> task, string because = "", params object[] becauseArgs) where TException : System.Exception where TInnerException : System.Exception { } @@ -1216,6 +1221,7 @@ namespace FluentAssertions.Events public OccurredEvent() { } public string EventName { get; set; } public object[] Parameters { get; set; } + public int Sequence { get; set; } public System.DateTime TimestampUtc { get; set; } } } @@ -1909,6 +1915,23 @@ namespace FluentAssertions.Primitives public FluentAssertions.AndConstraint NotBe(string unexpected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotBeEmpty(string because = "", params object[] becauseArgs) { } } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + public HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + } + public class HttpResponseMessageAssertions : FluentAssertions.Primitives.ObjectAssertions + where TAssertions : FluentAssertions.Primitives.HttpResponseMessageAssertions + { + protected HttpResponseMessageAssertions(System.Net.Http.HttpResponseMessage value) { } + protected override string Identifier { get; } + public FluentAssertions.AndConstraint BeRedirection(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint BeSuccessful(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveClientError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveServerError(string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint HaveStatusCode(System.Net.HttpStatusCode expected, string because = "", params object[] becauseArgs) { } + public FluentAssertions.AndConstraint NotHaveStatusCode(System.Net.HttpStatusCode unexpected, string because = "", params object[] becauseArgs) { } + } public class NullableBooleanAssertions : FluentAssertions.Primitives.NullableBooleanAssertions { public NullableBooleanAssertions(bool? value) { } @@ -2192,8 +2215,10 @@ namespace FluentAssertions.Specialized protected override string Identifier { get; } public TException Which { get; } public FluentAssertions.Specialized.ExceptionAssertions Where(System.Linq.Expressions.Expression> exceptionExpression, string because = "", params object[] becauseArgs) { } + public FluentAssertions.Specialized.ExceptionAssertions WithInnerException(System.Type innerException, string because = null, params object[] becauseArgs) { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithInnerException(string because = null, params object[] becauseArgs) where TInnerException : System.Exception { } + public FluentAssertions.Specialized.ExceptionAssertions WithInnerExceptionExactly(System.Type innerException, string because = null, params object[] becauseArgs) { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithInnerExceptionExactly(string because = null, params object[] becauseArgs) where TInnerException : System.Exception { } public virtual FluentAssertions.Specialized.ExceptionAssertions WithMessage(string expectedWildcardPattern, string because = "", params object[] becauseArgs) { } @@ -2267,6 +2292,7 @@ namespace FluentAssertions.Streams where TAssertions : FluentAssertions.Streams.BufferedStreamAssertions { public BufferedStreamAssertions(System.IO.BufferedStream stream) { } + protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveBufferSize(int expected, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveBufferSize(int unexpected, string because = "", params object[] becauseArgs) { } } @@ -2327,7 +2353,6 @@ namespace FluentAssertions.Types where TAssertions : FluentAssertions.Types.MethodBaseAssertions { protected MethodBaseAssertions(TSubject subject) { } - protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint NotHaveAccessModifier(FluentAssertions.Common.CSharpAccessModifier accessModifier, string because = "", params object[] becauseArgs) { } } @@ -2365,7 +2390,9 @@ namespace FluentAssertions.Types where TAttribute : System.Attribute { } public FluentAssertions.Types.MethodInfoSelector ThatAreNotDecoratedWithOrInherit() where TAttribute : System.Attribute { } + public FluentAssertions.Types.MethodInfoSelector ThatAreNotStatic() { } public FluentAssertions.Types.MethodInfoSelector ThatAreNotVirtual() { } + public FluentAssertions.Types.MethodInfoSelector ThatAreStatic() { } public FluentAssertions.Types.MethodInfoSelector ThatAreVirtual() { } public FluentAssertions.Types.MethodInfoSelector ThatDoNotReturn() { } public FluentAssertions.Types.MethodInfoSelector ThatReturn() { } @@ -2622,6 +2649,7 @@ namespace FluentAssertions.Xml public class XmlElementAssertions : FluentAssertions.Xml.XmlNodeAssertions { public XmlElementAssertions(System.Xml.XmlElement xmlElement) { } + protected override string Identifier { get; } public FluentAssertions.AndConstraint HaveAttribute(string expectedName, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndConstraint HaveAttributeWithNamespace(string expectedName, string expectedNamespace, string expectedValue, string because = "", params object[] becauseArgs) { } public FluentAssertions.AndWhichConstraint HaveElement(string expectedName, string because = "", params object[] becauseArgs) { } diff --git a/Tests/Benchmarks/Benchmarks.csproj b/Tests/Benchmarks/Benchmarks.csproj index 51b8040720..3e61198905 100644 --- a/Tests/Benchmarks/Benchmarks.csproj +++ b/Tests/Benchmarks/Benchmarks.csproj @@ -17,4 +17,4 @@ - \ No newline at end of file + diff --git a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainItemsAssignableTo.cs b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainItemsAssignableTo.cs index 524608e968..43f3518c2a 100644 --- a/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainItemsAssignableTo.cs +++ b/Tests/FluentAssertions.Specs/Collections/CollectionAssertionSpecs.ContainItemsAssignableTo.cs @@ -24,7 +24,7 @@ public void Should_succeed_when_asserting_collection_with_all_items_of_same_type } [Fact] - public void Should_fail_when_asserting_collection_with_items_of_different_types_only_contains_item_of_one_type() + public void Should_succeed_when_asserting_collection_with_items_of_different_types_contains_item_of_expected_type() { // Arrange var collection = new List @@ -33,68 +33,50 @@ public void Should_fail_when_asserting_collection_with_items_of_different_types_ "2" }; - // Act - Action act = () => collection.Should().ContainItemsAssignableTo(); - - // Assert - act.Should().Throw(); + // Act / Assert + collection.Should().ContainItemsAssignableTo(); } [Fact] - public void When_a_collection_contains_anything_other_than_strings_it_should_throw_and_report_details() + public void When_asserting_collection_contains_item_assignable_to_against_null_collection_it_should_throw() { // Arrange - var collection = new List - { - 1, - "2" - }; + int[] collection = null; // Act - Action act = () => collection.Should().ContainItemsAssignableTo(); + Action act = () => + { + using var _ = new AssertionScope(); + collection.Should().ContainItemsAssignableTo("because we want to test the behaviour with a null subject"); + }; // Assert act.Should().Throw().WithMessage( - "Expected collection to contain only items of type System.String, but item 1 at index 0 is of type System.Int32."); + "Expected collection to contain at least one element assignable to type \"System.String\" because we want to test the behaviour with a null subject, but found ."); } [Fact] - public void When_a_collection_contains_anything_other_than_strings_it_should_use_the_reason() + public void When_a_collection_is_empty_an_exception_should_be_thrown() { // Arrange - var collection = new List - { - 1, - "2" - }; + int[] collection = Array.Empty(); // Act - Action act = () => collection.Should().ContainItemsAssignableTo( - "because we want to test the failure {0}", "message"); + Action act = () => collection.Should().ContainItemsAssignableTo(); // Assert - act.Should().Throw() - .WithMessage( - "Expected collection to contain only items of type System.String because we want to test the failure message" + - ", but item 1 at index 0 is of type System.Int32."); + act.Should().Throw().WithMessage("Expected collection to contain at least one element assignable to type \"System.Int32\", but found {empty}."); } [Fact] - public void When_asserting_collection_contains_item_assignable_to_against_null_collection_it_should_throw() + public void Should_throw_exception_when_asserting_collection_for_missing_item_type() { - // Arrange - int[] collection = null; + var collection = new object[] { "1", 1.0m }; - // Act - Action act = () => - { - using var _ = new AssertionScope(); - collection.Should().ContainItemsAssignableTo("because we want to test the behaviour with a null subject"); - }; + Action act = () => collection.Should().ContainItemsAssignableTo(); - // Assert - act.Should().Throw().WithMessage( - "Expected collection to contain element assignable to type System.String because we want to test the behaviour with a null subject, but found ."); + act.Should().Throw() + .WithMessage("Expected collection to contain at least one element assignable to type \"System.Int32\", but found {System.String, System.Decimal}."); } #endregion diff --git a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs index a60f261f50..86c451cf7d 100644 --- a/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Collections/GenericDictionaryAssertionSpecs.cs @@ -874,7 +874,7 @@ public void When_asserting_dictionary_with_items_is_not_empty_it_should_succeed( dictionary.Should().NotBeEmpty(); } - #if !NET5_0_OR_GREATER +#if !NET5_0_OR_GREATER [Fact] public void When_asserting_dictionary_with_items_is_not_empty_it_should_enumerate_the_dictionary_only_once() @@ -889,7 +889,7 @@ public void When_asserting_dictionary_with_items_is_not_empty_it_should_enumerat trackingDictionary.Enumerator.LoopCount.Should().Be(1); } - #endif +#endif [Fact] public void When_asserting_dictionary_without_items_is_not_empty_it_should_fail() @@ -2638,6 +2638,15 @@ public void When_a_dictionary_like_collection_contains_the_expected_key_it_shoul act.Should().NotThrow(); } + [Theory] + [MemberData(nameof(SingleDictionaryData))] + public void When_a_dictionary_like_collection_contains_the_expected_key_and_value_it_should_succeed(T subject) + where T : IEnumerable> + { + // Assert + subject.Should().Contain(1, 42); + } + [Theory] [MemberData(nameof(SingleDictionaryData))] public void When_a_dictionary_like_collection_contains_the_expected_value_it_should_succeed(T subject) @@ -2675,6 +2684,19 @@ private static object[] Dictionaries() }; } + [Fact] + public void When_a_dictionary_like_collection_contains_the_default_key_it_should_succeed() + { + // Arrange + var subject = new List>() { new(0, 0) }; + + // Act + Action act = () => subject.Should().Contain(0, 0); + + // Assert + act.Should().NotThrow(); + } + /// /// This class only implements , /// as also implements . diff --git a/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs index 432996e070..f07bc28c78 100644 --- a/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Events/EventAssertionSpecs.cs @@ -411,6 +411,28 @@ public void When_constraints_are_specified_it_should_filter_the_events_based_on_ .Which.PropertyName.Should().Be("Boo"); } + [Fact] + public void When_events_are_raised_regardless_of_time_tick_it_should_return_by_invokation_order() + { + // Arrange + var observable = new TestEventRaisingInOrder(); + var utcNow = 11.January(2022).At(12, 00).AsUtc(); + using var monitor = observable.Monitor(() => utcNow); + + // Act + observable.RaiseAllEvents(); + + // Assert + monitor.OccurredEvents[0].EventName.Should().Be(nameof(TestEventRaisingInOrder.InterfaceEvent)); + monitor.OccurredEvents[0].Sequence.Should().Be(0); + + monitor.OccurredEvents[1].EventName.Should().Be(nameof(TestEventRaisingInOrder.Interface2Event)); + monitor.OccurredEvents[1].Sequence.Should().Be(1); + + monitor.OccurredEvents[2].EventName.Should().Be(nameof(TestEventRaisingInOrder.Interface3Event)); + monitor.OccurredEvents[2].Sequence.Should().Be(2); + } + #endregion #region Should(Not)RaisePropertyChanged events @@ -817,6 +839,22 @@ public void RaiseBothEvents() } } + private class TestEventRaisingInOrder : IEventRaisingInterface, IEventRaisingInterface2, IEventRaisingInterface3 + { + public event EventHandler Interface3Event; + + public event EventHandler Interface2Event; + + public event EventHandler InterfaceEvent; + + public void RaiseAllEvents() + { + InterfaceEvent?.Invoke(this, EventArgs.Empty); + Interface2Event?.Invoke(this, EventArgs.Empty); + Interface3Event?.Invoke(this, EventArgs.Empty); + } + } + public interface IEventRaisingInterface { event EventHandler InterfaceEvent; @@ -827,6 +865,11 @@ public interface IEventRaisingInterface2 event EventHandler Interface2Event; } + public interface IEventRaisingInterface3 + { + event EventHandler Interface3Event; + } + public interface IInheritsEventRaisingInterface : IEventRaisingInterface { } diff --git a/Tests/FluentAssertions.Specs/Exceptions/AsyncFunctionExceptionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/AsyncFunctionExceptionAssertionSpecs.cs index 4c2558ffb5..f454271169 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/AsyncFunctionExceptionAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/AsyncFunctionExceptionAssertionSpecs.cs @@ -832,6 +832,21 @@ public async Task When_async_method_throws_the_expected_inner_exception_it_shoul await action.Should().NotThrowAsync(); } + [Fact] + public async Task When_async_method_throws_the_expected_inner_exception_from_argument_it_should_succeed() + { + // Arrange + Func task = () => Throw.Async(new AggregateException(new InvalidOperationException())); + + // Act + Func action = () => task + .Should().ThrowAsync() + .WithInnerException(typeof(InvalidOperationException)); + + // Assert + await action.Should().NotThrowAsync(); + } + [Fact] public async Task When_async_method_throws_the_expected_inner_exception_exactly_it_should_succeed() { @@ -847,6 +862,21 @@ public async Task When_async_method_throws_the_expected_inner_exception_exactly_ await action.Should().NotThrowAsync(); } + [Fact] + public async Task When_async_method_throws_the_expected_inner_exception_exactly_defined_in_arguments_it_should_succeed() + { + // Arrange + Func task = () => Throw.Async(new AggregateException(new ArgumentException())); + + // Act + Func action = () => task + .Should().ThrowAsync() + .WithInnerExceptionExactly(typeof(ArgumentException)); + + // Assert + await action.Should().NotThrowAsync(); + } + [Fact] public async Task When_async_method_throws_aggregate_exception_containing_expected_exception_it_should_succeed() { @@ -890,6 +920,21 @@ public async Task When_async_method_does_not_throw_the_expected_inner_exception_ await action.Should().ThrowAsync().WithMessage("*InvalidOperation*Argument*"); } + [Fact] + public async Task When_async_method_does_not_throw_the_expected_inner_exception_from_argument_it_should_fail() + { + // Arrange + Func task = () => Throw.Async(new AggregateException(new ArgumentException())); + + // Act + Func action = () => task + .Should().ThrowAsync() + .WithInnerException(typeof(InvalidOperationException)); + + // Assert + await action.Should().ThrowAsync().WithMessage("*InvalidOperation*Argument*"); + } + [Fact] public async Task When_async_method_does_not_throw_the_expected_inner_exception_exactly_it_should_fail() { @@ -905,6 +950,22 @@ public async Task When_async_method_does_not_throw_the_expected_inner_exception_ await action.Should().ThrowAsync().WithMessage("*ArgumentException*ArgumentNullException*"); } + [Fact] + public async Task + When_async_method_does_not_throw_the_expected_inner_exception_exactly_defined_in_arguments_it_should_fail() + { + // Arrange + Func task = () => Throw.Async(new AggregateException(new ArgumentNullException())); + + // Act + Func action = () => task + .Should().ThrowAsync() + .WithInnerExceptionExactly(typeof(ArgumentException)); + + // Assert + await action.Should().ThrowAsync().WithMessage("*ArgumentException*ArgumentNullException*"); + } + [Fact] public async Task When_async_method_does_not_throw_the_expected_exception_it_should_fail() { diff --git a/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs b/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs index 36085cbe62..8562dc40e2 100644 --- a/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs +++ b/Tests/FluentAssertions.Specs/Exceptions/ExceptionAssertionSpecs.cs @@ -409,6 +409,22 @@ public void When_asserting_with_an_aggregate_exception_type_the_asserts_should_o .WithMessage("Inner Message"); } + [Fact] + public void When_asserting_with_an_aggregate_exception_and_inner_exception_type_from_argument_the_asserts_should_occur_against_the_aggregate_exception() + { + // Arrange + Does testSubject = Does.Throw(new AggregateException("Outer Message", new Exception("Inner Message"))); + + // Act + Action act = testSubject.Do; + + // Assert + act.Should().Throw() + .WithMessage("Outer Message*") + .WithInnerException(typeof(Exception)) + .WithMessage("Inner Message"); + } + #endregion #region Inner Exceptions @@ -426,6 +442,32 @@ public void When_subject_throws_an_exception_with_the_expected_inner_exception_i .WithInnerException(); } + [Fact] + public void When_subject_throws_an_exception_with_the_expected_inner_base_exception_it_should_not_do_anything() + { + // Arrange + Does testSubject = Does.Throw(new Exception("", new ArgumentNullException())); + + // Act / Assert + testSubject + .Invoking(x => x.Do()) + .Should().Throw() + .WithInnerException(); + } + + [Fact] + public void When_subject_throws_an_exception_with_the_expected_inner_exception_from_argument_it_should_not_do_anything() + { + // Arrange + Does testSubject = Does.Throw(new Exception("", new ArgumentException())); + + // Act / Assert + testSubject + .Invoking(x => x.Do()) + .Should().Throw() + .WithInnerException(typeof(ArgumentException)); + } + [Fact] public void WithInnerExceptionExactly_no_parameters_when_subject_throws_subclass_of_expected_inner_exception_it_should_throw_with_clear_description() { @@ -487,6 +529,53 @@ public void WithInnerExceptionExactly_when_subject_throws_subclass_of_expected_i } } + [Fact] + public void WithInnerExceptionExactly_with_type_exception_when_subject_throws_expected_inner_exception_it_should_not_do_anything() + { + // Arrange + Action act = () => throw new BadImageFormatException("", new ArgumentNullException()); + + // Act / Assert + act.Should().Throw() + .WithInnerExceptionExactly(typeof(ArgumentNullException), "because {0} should do just that", "the action"); + } + + [Fact] + public void WithInnerExceptionExactly_with_type_exception_no_parameters_when_subject_throws_expected_inner_exception_it_should_not_do_anything() + { + // Arrange + Action act = () => throw new BadImageFormatException("", new ArgumentNullException()); + + // Act / Assert + act.Should().Throw() + .WithInnerExceptionExactly(typeof(ArgumentNullException)); + } + + [Fact] + public void WithInnerExceptionExactly_with_type_exception_when_subject_throws_subclass_of_expected_inner_exception_it_should_throw_with_clear_description() + { + // Arrange + var innerException = new ArgumentNullException(); + + Action act = () => throw new BadImageFormatException("", innerException); + + try + { + // Act + act.Should().Throw() + .WithInnerExceptionExactly(typeof(ArgumentException), "because {0} should do just that", "the action"); + + throw new XunitException("This point should not be reached."); + } + catch (XunitException ex) + { + // Assert + var expectedMessage = BuildExpectedMessageForWithInnerExceptionExactly("Expected inner System.ArgumentException because the action should do just that, but found System.ArgumentNullException with message", innerException.Message); + + ex.Message.Should().Be(expectedMessage); + } + } + [Fact] public void WithInnerExceptionExactly_when_subject_throws_expected_inner_exception_it_should_not_do_anything() { @@ -585,6 +674,20 @@ public void When_an_inner_exception_matches_exactly_it_should_allow_chaining_mor .Where(i => i.Message == "InnerMessage"); } + [Fact] + public void When_an_inner_exception_matches_exactly_it_should_allow_chaining_more_asserts_on_that_exception_type_from_argument() + { + // Act + Action act = () => + throw new ArgumentException("OuterMessage", new InvalidOperationException("InnerMessage")); + + // Assert + act + .Should().ThrowExactly() + .WithInnerExceptionExactly(typeof(InvalidOperationException)) + .Where(i => i.Message == "InnerMessage"); + } + [Fact] public void When_injecting_a_null_predicate_it_should_throw() { diff --git a/Tests/FluentAssertions.Specs/Execution/CallerIdentifierSpecs.cs b/Tests/FluentAssertions.Specs/Execution/CallerIdentifierSpecs.cs index 96098ebde3..a539f4f942 100644 --- a/Tests/FluentAssertions.Specs/Execution/CallerIdentifierSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/CallerIdentifierSpecs.cs @@ -452,6 +452,22 @@ public void When_the_method_has_Should_prefix_it_should_read_whole_method() act.Should().Throw() .WithMessage("Expected foo.ShouldReturnSomeBool() to be false*"); } + + [UIFact] + public async Task Caller_identification_should_also_work_for_statements_following_async_code() + { + // Arrange + const string someText = "Hello"; + Func task = async () => await Task.Yield(); + + // Act + await task.Should().NotThrowAsync(); + Action act = () => someText.Should().Be("Hi"); + + // Assert + act.Should().Throw() + .WithMessage("*someText*", "it should capture the variable name"); + } } [SuppressMessage("The name of a C# element does not begin with an upper-case letter", "SA1300")] diff --git a/Tests/FluentAssertions.Specs/Execution/GivenSelectorSpecs.cs b/Tests/FluentAssertions.Specs/Execution/GivenSelectorSpecs.cs index df431da601..d2a73ae8ae 100644 --- a/Tests/FluentAssertions.Specs/Execution/GivenSelectorSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/GivenSelectorSpecs.cs @@ -10,7 +10,7 @@ public class GivenSelectorSpecs [Fact] public void A_consecutive_subject_should_be_selected() { - // Arange + // Arrange string value = string.Empty; // Act @@ -26,7 +26,7 @@ public void A_consecutive_subject_should_be_selected() [Fact] public void After_a_failed_condition_a_consecutive_subject_should_be_ignored() { - // Arange + // Arrange string value = string.Empty; // Act diff --git a/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj b/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj index 5c7e276c02..9845cf5918 100644 --- a/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj +++ b/Tests/FluentAssertions.Specs/FluentAssertions.Specs.csproj @@ -1,5 +1,6 @@ + net47;net5.0;netcoreapp2.0;netcoreapp2.1;netcoreapp3.0 9.0 @@ -8,24 +9,24 @@ false ..\..\TestRules.ruleset - $(OutputPath)$(AssemblyName).xml + $(OutputPath)$(AssemblyName).xml $(NoWarn),IDE0052,1573,1591,1712 false true + full + + + + false + false + false - + - - full - - - full - - - +