From a2ad855e99e61c97456a1597e4b3c1606daf8916 Mon Sep 17 00:00:00 2001 From: Dennis Doomen Date: Fri, 20 Dec 2024 21:03:08 +0100 Subject: [PATCH] SAVEPOINT --- .editorconfig | 5 + Src/FluentAssertions/AndWhichConstraint.cs | 3 +- .../AssertionConfiguration.cs | 11 + .../Services.cs => AssertionEngine.cs} | 58 +++-- Src/FluentAssertions/AssertionExtensions.cs | 2 +- .../GenericCollectionAssertions.cs | 32 +-- .../GenericDictionaryAssertions.cs | 8 +- .../Collections/StringCollectionAssertions.cs | 8 +- .../Common/AppSettingsConfigurationStore.cs | 17 -- Src/FluentAssertions/Common/Configuration.cs | 130 ---------- .../ConfigurationStoreExceptionInterceptor.cs | 31 --- .../Common/IConfigurationStore.cs | 6 - Src/FluentAssertions/Common/IReflector.cs | 10 - .../Common/NullConfigurationStore.cs | 9 - ...FrameworkReflector.cs => TypeReflector.cs} | 5 +- .../Common/ValueFormatterDetectionMode.cs | 10 +- .../Configuration/GlobalConfiguration.cs | 10 + .../GlobalEquivalencyOptions.cs} | 61 ++--- .../Configuration/GlobalFormattingOptions.cs | 11 + .../{ => Equivalency}/EquivalencyPlan.cs | 17 +- .../Equivalency/EquivalencyValidator.cs | 2 +- .../Execution/AssertionScope.cs | 2 +- .../Execution/CollectingAssertionStrategy.cs | 3 +- .../Execution/DefaultAssertionStrategy.cs | 3 +- .../Execution/ITestFramework.cs | 2 +- ...orkProvider.cs => TestFrameworkFactory.cs} | 43 ++-- .../Formatting/AttributeBasedFormatter.cs | 27 ++- .../Formatting/FormattedObjectGraph.cs | 2 +- Src/FluentAssertions/Formatting/Formatter.cs | 2 +- .../Formatting/FormattingOptions.cs | 6 +- .../Numeric/ComparableTypeAssertions.cs | 6 +- .../ObjectAssertionsExtensions.cs | 4 +- .../Primitives/ObjectAssertions.cs | 10 +- .../Primitives/StringAssertions.cs | 14 +- .../Specialized/ExceptionAssertions.cs | 2 +- .../FluentAssertions/net47.verified.txt | 103 ++++---- .../FluentAssertions/net6.0.verified.txt | 103 ++++---- .../netstandard2.0.verified.txt | 103 ++++---- .../netstandard2.1.verified.txt | 103 ++++---- .../Common/ConfigurationSpecs.cs | 81 ------- .../AssertionConfigurationSpecs.cs | 31 +++ .../GlobalEquivalencyOptionsSpecs.cs} | 42 ++-- .../TestFrameworkProviderSpecs.cs | 56 +++++ .../ConfigurationSpecs.cs | 36 --- .../Execution/AssertionScopeSpecs.cs | 5 +- .../Execution/FallbackTestFrameworkTests.cs | 24 -- .../Execution/TestFrameworkProviderTests.cs | 84 ------- .../Formatting/FormatterSpecs.cs | 228 +++++++++--------- 48 files changed, 629 insertions(+), 942 deletions(-) create mode 100644 Src/FluentAssertions/AssertionConfiguration.cs rename Src/FluentAssertions/{Common/Services.cs => AssertionEngine.cs} (73%) delete mode 100644 Src/FluentAssertions/Common/AppSettingsConfigurationStore.cs delete mode 100644 Src/FluentAssertions/Common/Configuration.cs delete mode 100644 Src/FluentAssertions/Common/ConfigurationStoreExceptionInterceptor.cs delete mode 100644 Src/FluentAssertions/Common/IConfigurationStore.cs delete mode 100644 Src/FluentAssertions/Common/IReflector.cs delete mode 100644 Src/FluentAssertions/Common/NullConfigurationStore.cs rename Src/FluentAssertions/Common/{FullFrameworkReflector.cs => TypeReflector.cs} (90%) create mode 100644 Src/FluentAssertions/Configuration/GlobalConfiguration.cs rename Src/FluentAssertions/{AssertionOptions.cs => Configuration/GlobalEquivalencyOptions.cs} (50%) create mode 100644 Src/FluentAssertions/Configuration/GlobalFormattingOptions.cs rename Src/FluentAssertions/{ => Equivalency}/EquivalencyPlan.cs (82%) rename Src/FluentAssertions/Execution/{TestFrameworkProvider.cs => TestFrameworkFactory.cs} (68%) delete mode 100644 Tests/FluentAssertions.Specs/Common/ConfigurationSpecs.cs create mode 100644 Tests/FluentAssertions.Specs/Configuration/AssertionConfigurationSpecs.cs rename Tests/FluentAssertions.Specs/{AssertionOptionsSpecs.cs => Configuration/GlobalEquivalencyOptionsSpecs.cs} (88%) create mode 100644 Tests/FluentAssertions.Specs/Configuration/TestFrameworkProviderSpecs.cs delete mode 100644 Tests/FluentAssertions.Specs/ConfigurationSpecs.cs delete mode 100644 Tests/FluentAssertions.Specs/Execution/FallbackTestFrameworkTests.cs delete mode 100644 Tests/FluentAssertions.Specs/Execution/TestFrameworkProviderTests.cs diff --git a/.editorconfig b/.editorconfig index 0ea4ede3a9..341cae8083 100644 --- a/.editorconfig +++ b/.editorconfig @@ -146,6 +146,11 @@ dotnet_diagnostic.CA1307.severity = error dotnet_diagnostic.CA1308.severity = error # CA1309: Use ordinal StringComparison dotnet_diagnostic.CA1309.severity = error + +# Purpose: Rename virtual/interface member ITestFramework.Throw(string) so that it no longer conflicts with the reserved language keyword 'Throw' +# Reason: We don't care about other languages than C# +dotnet_diagnostic.CA1716.severity = none + # CA1724: Type names should not match namespaces dotnet_diagnostic.CA1724.severity = none # CA1819: Properties should not return arrays diff --git a/Src/FluentAssertions/AndWhichConstraint.cs b/Src/FluentAssertions/AndWhichConstraint.cs index 858423955f..49df87657e 100644 --- a/Src/FluentAssertions/AndWhichConstraint.cs +++ b/Src/FluentAssertions/AndWhichConstraint.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using FluentAssertions.Common; using FluentAssertions.Execution; using FluentAssertions.Formatting; @@ -84,7 +83,7 @@ private static TSubject Single(IEnumerable subjects) string message = "More than one object found. FluentAssertions cannot determine which object is meant." + $" Found objects:{Environment.NewLine}{foundObjects}"; - Services.ThrowException(message); + AssertionEngine.TestFramework.Throw(message); } return matchedElements.Single(); diff --git a/Src/FluentAssertions/AssertionConfiguration.cs b/Src/FluentAssertions/AssertionConfiguration.cs new file mode 100644 index 0000000000..82a2833716 --- /dev/null +++ b/Src/FluentAssertions/AssertionConfiguration.cs @@ -0,0 +1,11 @@ +using FluentAssertions.Configuration; + +namespace FluentAssertions; + +/// +/// Provides access to the global configuration and options to customize the behavior of FluentAssertions. +/// +public static class AssertionConfiguration +{ + public static GlobalConfiguration Current => AssertionEngine.Configuration; +} diff --git a/Src/FluentAssertions/Common/Services.cs b/Src/FluentAssertions/AssertionEngine.cs similarity index 73% rename from Src/FluentAssertions/Common/Services.cs rename to Src/FluentAssertions/AssertionEngine.cs index 551b09f2d2..27b03d18ed 100644 --- a/Src/FluentAssertions/Common/Services.cs +++ b/Src/FluentAssertions/AssertionEngine.cs @@ -1,47 +1,29 @@ using System; using System.Linq; using System.Reflection; +using FluentAssertions.Configuration; using FluentAssertions.Execution; using FluentAssertions.Extensibility; using JetBrains.Annotations; -namespace FluentAssertions.Common; +namespace FluentAssertions; -/// -/// Maintains the framework-specific services. -/// -public static class Services +public static class AssertionEngine { private static readonly object Lockable = new(); - private static Configuration configuration; private static bool isInitialized; + private static GlobalConfiguration configuration; - static Services() + static AssertionEngine() { EnsureInitialized(); } - public static IConfigurationStore ConfigurationStore { get; set; } - - public static Configuration Configuration - { - get - { - lock (Lockable) - { - return configuration ??= new Configuration(ConfigurationStore); - } - } - } - - public static Action ThrowException { get; set; } - - public static IReflector Reflector { get; set; } - [PublicAPI] public static void ResetToDefaults() { isInitialized = false; + configuration = null; EnsureInitialized(); } @@ -58,19 +40,15 @@ internal static void EnsureInitialized() { ExecuteCustomInitializers(); - Reflector = new FullFrameworkReflector(); -#if NETFRAMEWORK || NET6_0_OR_GREATER - ConfigurationStore = new ConfigurationStoreExceptionInterceptor(new AppSettingsConfigurationStore()); -#else - ConfigurationStore = new NullConfigurationStore(); -#endif - ThrowException = new TestFrameworkProvider(Configuration).Throw; + TestFramework = TestFrameworkFactory.GetFramework(Configuration.TestFrameworkName); isInitialized = true; } } } + public static ITestFramework TestFramework { get; set; } + private static void ExecuteCustomInitializers() { var currentAssembly = Assembly.GetExecutingAssembly(); @@ -105,6 +83,24 @@ private static void ExecuteCustomInitializers() } } + public static GlobalConfiguration Configuration + { + get + { + if (configuration is not null) + { + return configuration; + } + + lock (Lockable) + { + configuration = new GlobalConfiguration(); + + return configuration; + } + } + } + private static bool IsFramework(Assembly assembly) { #if NET6_0_OR_GREATER diff --git a/Src/FluentAssertions/AssertionExtensions.cs b/Src/FluentAssertions/AssertionExtensions.cs index 4832352cf5..4ff21a5157 100644 --- a/Src/FluentAssertions/AssertionExtensions.cs +++ b/Src/FluentAssertions/AssertionExtensions.cs @@ -36,7 +36,7 @@ public static class AssertionExtensions static AssertionExtensions() { - Services.EnsureInitialized(); + AssertionEngine.EnsureInitialized(); } /// diff --git a/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs b/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs index bdb58c40c7..ebc69508e9 100644 --- a/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/GenericCollectionAssertions.cs @@ -137,7 +137,7 @@ public AndConstraint AllBeAssignableTo(Type expectedType, /// and the result is equal. /// The type of a collection property is ignored as long as the collection implements and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// The expected element. /// @@ -162,14 +162,14 @@ public AndConstraint AllBeEquivalentTo(TExpectation e /// and the result is equal. /// The type of a collection property is ignored as long as the collection implements and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// The expected element. /// /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the /// class. The global defaults are determined by the - /// class. + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -309,7 +309,7 @@ public AndConstraint BeEmpty([StringSyntax("CompositeFormat")] stri /// and the result is equal. /// The type of a collection property is ignored as long as the collection implements and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// An with the expected elements. /// @@ -334,14 +334,14 @@ public AndConstraint BeEquivalentTo(IEnumerable and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// An with the expected elements. /// /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the /// class. The global defaults are determined by the - /// class. + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -359,7 +359,7 @@ public AndConstraint BeEquivalentTo(IEnumerable> options = - config(AssertionOptions.CloneDefaults()).AsCollection(); + config(AssertionConfiguration.Current.Equivalency.CloneDefaults()).AsCollection(); var context = new EquivalencyValidationContext( @@ -844,7 +844,7 @@ public AndConstraint Contain(IEnumerable expected, [StringSyntax /// /// By default, objects within the collection are seen as equivalent to the expected object when both object graphs have equally named properties with the same /// value, irrespective of the type of those objects. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// /// The expected element. @@ -873,7 +873,7 @@ public AndWhichConstraint ContainEquivalentOf(TExp /// /// By default, objects within the collection are seen as equivalent to the expected object when both object graphs have equally named properties with the same /// value, irrespective of the type of those objects. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// /// The expected element. @@ -881,7 +881,7 @@ public AndWhichConstraint ContainEquivalentOf(TExp /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the /// class. The global defaults are determined by the - /// class. + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -905,7 +905,7 @@ public AndWhichConstraint ContainEquivalentOf(TExp if (assertionChain.Succeeded) { - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); using var scope = new AssertionScope(); assertionChain.AddReportable("configuration", () => options.ToString()); @@ -1849,7 +1849,7 @@ public AndConstraint NotBeEquivalentTo(IEnumerable configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the /// class. The global defaults are determined by the - /// class. + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -2339,7 +2339,7 @@ public AndConstraint NotContain(IEnumerable unexpected, [StringS /// /// By default, objects within the collection are seen as not equivalent to the expected object when both object graphs have unequally named properties with the same /// value, irrespective of the type of those objects. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// /// The unexpected element. @@ -2368,7 +2368,7 @@ public AndConstraint NotContainEquivalentOf(TExpectat /// /// By default, objects within the collection are seen as not equivalent to the expected object when both object graphs have unequally named properties with the same /// value, irrespective of the type of those objects. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// /// The unexpected element. @@ -2376,7 +2376,7 @@ public AndConstraint NotContainEquivalentOf(TExpectat /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the /// class. The global defaults are determined by the - /// class. + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -2402,7 +2402,7 @@ public AndConstraint NotContainEquivalentOf(TExpectat if (assertionChain.Succeeded) { - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var foundIndices = new List(); diff --git a/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs b/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs index 381e082a1b..51331ba9c7 100644 --- a/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs +++ b/Src/FluentAssertions/Collections/GenericDictionaryAssertions.cs @@ -172,7 +172,7 @@ public AndConstraint NotEqual(T unexpected, /// and the result is equal. /// The type of the values in the dictionaries are ignored as long as both dictionaries contain the same keys and /// the values for each key are structurally equivalent. Notice that actual behavior is determined by the global - /// defaults managed by the class. + /// defaults managed by the class. /// /// The expected element. /// @@ -197,14 +197,14 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// and the result is equal. /// The type of the values in the dictionaries are ignored as long as both dictionaries contain the same keys and /// the values for each key are structurally equivalent. Notice that actual behavior is determined by the global - /// defaults managed by the class. + /// defaults managed by the class. /// /// The expected element. /// /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the /// class. The global defaults are determined by the - /// class. + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -220,7 +220,7 @@ public AndConstraint BeEquivalentTo(TExpectation expe { Guard.ThrowIfArgumentIsNull(config); - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var context = new EquivalencyValidationContext(Node.From(() => CurrentAssertionChain.CallerIdentifier), options) diff --git a/Src/FluentAssertions/Collections/StringCollectionAssertions.cs b/Src/FluentAssertions/Collections/StringCollectionAssertions.cs index 6d2fce15ca..f6a9482bee 100644 --- a/Src/FluentAssertions/Collections/StringCollectionAssertions.cs +++ b/Src/FluentAssertions/Collections/StringCollectionAssertions.cs @@ -113,8 +113,8 @@ public AndConstraint BeEquivalentTo(IEnumerable expectation /// /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the - /// class. The global defaults are determined by the - /// class. + /// class. The global defaults can be modified through + /// . /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -131,7 +131,7 @@ public AndConstraint BeEquivalentTo(IEnumerable expectation Guard.ThrowIfArgumentIsNull(config); EquivalencyOptions> - options = config(AssertionOptions.CloneDefaults()).AsCollection(); + options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()).AsCollection(); var context = new EquivalencyValidationContext(Node.From>(() => CurrentAssertionChain.CallerIdentifier), options) @@ -177,7 +177,7 @@ public AndConstraint AllBe(string expectation, /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the /// class. The global defaults are determined by the - /// class. + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion diff --git a/Src/FluentAssertions/Common/AppSettingsConfigurationStore.cs b/Src/FluentAssertions/Common/AppSettingsConfigurationStore.cs deleted file mode 100644 index ee12469337..0000000000 --- a/Src/FluentAssertions/Common/AppSettingsConfigurationStore.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Even though .NET Standard 2.0 seems to support the ConfigurationManager class according -// to the NuGet package at https://www.nuget.org/packages/System.Configuration.ConfigurationManager, -// it will often throw a PlatformNotSupport exception. See -// https://docs.microsoft.com/en-us/dotnet/api/system.configuration.configurationmanager?view=netframework-4.8 - -using System.Configuration; - -namespace FluentAssertions.Common; - -internal class AppSettingsConfigurationStore : IConfigurationStore -{ - public string GetSetting(string name) - { - string value = ConfigurationManager.AppSettings[name]; - return !string.IsNullOrEmpty(value) ? value : null; - } -} diff --git a/Src/FluentAssertions/Common/Configuration.cs b/Src/FluentAssertions/Common/Configuration.cs deleted file mode 100644 index a74cee2744..0000000000 --- a/Src/FluentAssertions/Common/Configuration.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using FluentAssertions.Formatting; - -namespace FluentAssertions.Common; - -public class Configuration -{ - /// - /// Defines the key for the configuration of the test framework to be assumed in FluentAssertions. - /// - private const string TestFrameworkConfigurationKey = "FluentAssertions.TestFramework"; - - #region Private Definitions - - private readonly object propertiesAccessLock = new(); - private readonly IConfigurationStore store; - private string valueFormatterAssembly; - private ValueFormatterDetectionMode? valueFormatterDetectionMode; - private string testFrameworkName; - - #endregion - - /// - /// Gets the active configuration, - /// - public static Configuration Current => Services.Configuration; - - public Configuration(IConfigurationStore store) - { - this.store = store; - } - - /// - /// Gets or sets the mode on how Fluent Assertions will find custom implementations of - /// . - /// - public ValueFormatterDetectionMode ValueFormatterDetectionMode - { - get - { - lock (propertiesAccessLock) - { - return valueFormatterDetectionMode ??= DetermineFormatterDetectionMode(); - } - } - - set - { - valueFormatterDetectionMode = value; - } - } - - private ValueFormatterDetectionMode DetermineFormatterDetectionMode() - { - if (ValueFormatterAssembly is not null) - { - return ValueFormatterDetectionMode.Specific; - } - - string setting = store.GetSetting("valueFormatters"); - - if (!string.IsNullOrEmpty(setting)) - { - try - { - return (ValueFormatterDetectionMode)Enum.Parse(typeof(ValueFormatterDetectionMode), setting, ignoreCase: true); - } - catch (ArgumentException) - { - throw new InvalidOperationException( - $"'{setting}' is not a valid option for detecting value formatters. Valid options include Disabled, Specific and Scan."); - } - } - - return ValueFormatterDetectionMode.Disabled; - } - - /// - /// Gets or sets the assembly name to scan for custom value formatters in case - /// is set to . - /// - public string ValueFormatterAssembly - { - get - { - if (valueFormatterAssembly is null) - { - string assemblyName = store.GetSetting("valueFormattersAssembly"); - - if (!string.IsNullOrEmpty(assemblyName)) - { - valueFormatterAssembly = assemblyName; - } - } - - return valueFormatterAssembly; - } - - set - { - lock (propertiesAccessLock) - { - valueFormatterAssembly = value; - valueFormatterDetectionMode = null; - } - } - } - - /// - /// Gets or sets the name of the test framework to use. - /// - /// - /// If no name is provided, Fluent Assertions - /// will try to detect it by scanning the currently loaded assemblies. If it can't find a suitable provider, - /// and the run-time platform supports it, it'll try to get it from the . - /// - public string TestFrameworkName - { - get - { - if (string.IsNullOrEmpty(testFrameworkName)) - { - testFrameworkName = store.GetSetting(TestFrameworkConfigurationKey); - } - - return testFrameworkName; - } - set => testFrameworkName = value; - } -} diff --git a/Src/FluentAssertions/Common/ConfigurationStoreExceptionInterceptor.cs b/Src/FluentAssertions/Common/ConfigurationStoreExceptionInterceptor.cs deleted file mode 100644 index e4760a6c6b..0000000000 --- a/Src/FluentAssertions/Common/ConfigurationStoreExceptionInterceptor.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace FluentAssertions.Common; - -internal class ConfigurationStoreExceptionInterceptor : IConfigurationStore -{ - private readonly IConfigurationStore configurationStore; - - private bool underlyingStoreUnavailable; - - public ConfigurationStoreExceptionInterceptor(IConfigurationStore configurationStore) - { - this.configurationStore = configurationStore; - } - - public string GetSetting(string name) - { - if (underlyingStoreUnavailable) - { - return null; - } - - try - { - return configurationStore.GetSetting(name); - } - catch - { - underlyingStoreUnavailable = true; - return null; - } - } -} diff --git a/Src/FluentAssertions/Common/IConfigurationStore.cs b/Src/FluentAssertions/Common/IConfigurationStore.cs deleted file mode 100644 index c8ce460506..0000000000 --- a/Src/FluentAssertions/Common/IConfigurationStore.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace FluentAssertions.Common; - -public interface IConfigurationStore -{ - string GetSetting(string name); -} diff --git a/Src/FluentAssertions/Common/IReflector.cs b/Src/FluentAssertions/Common/IReflector.cs deleted file mode 100644 index 90b9c9ff84..0000000000 --- a/Src/FluentAssertions/Common/IReflector.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace FluentAssertions.Common; - -public interface IReflector -{ - IEnumerable GetAllTypesFromAppDomain(Func predicate); -} diff --git a/Src/FluentAssertions/Common/NullConfigurationStore.cs b/Src/FluentAssertions/Common/NullConfigurationStore.cs deleted file mode 100644 index b25caf75cf..0000000000 --- a/Src/FluentAssertions/Common/NullConfigurationStore.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace FluentAssertions.Common; - -internal class NullConfigurationStore : IConfigurationStore -{ - public string GetSetting(string name) - { - return string.Empty; - } -} diff --git a/Src/FluentAssertions/Common/FullFrameworkReflector.cs b/Src/FluentAssertions/Common/TypeReflector.cs similarity index 90% rename from Src/FluentAssertions/Common/FullFrameworkReflector.cs rename to Src/FluentAssertions/Common/TypeReflector.cs index f9fba7a4a8..d630c2f0a3 100644 --- a/Src/FluentAssertions/Common/FullFrameworkReflector.cs +++ b/Src/FluentAssertions/Common/TypeReflector.cs @@ -6,9 +6,9 @@ namespace FluentAssertions.Common; -internal class FullFrameworkReflector : IReflector +internal static class TypeReflector { - public IEnumerable GetAllTypesFromAppDomain(Func predicate) + public static IEnumerable GetAllTypesFromAppDomain(Func predicate) { return AppDomain.CurrentDomain .GetAssemblies() @@ -21,6 +21,7 @@ private static bool IsRelevant(Assembly ass) string assemblyName = ass.GetName().Name; return + assemblyName is not null && !assemblyName.StartsWith("microsoft.", StringComparison.OrdinalIgnoreCase) && !assemblyName.StartsWith("xunit", StringComparison.OrdinalIgnoreCase) && !assemblyName.StartsWith("jetbrains.", StringComparison.OrdinalIgnoreCase) && diff --git a/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs b/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs index 8d8d170950..cc938cc6a2 100644 --- a/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs +++ b/Src/FluentAssertions/Common/ValueFormatterDetectionMode.cs @@ -1,8 +1,10 @@ -namespace FluentAssertions.Common; +using FluentAssertions.Configuration; + +namespace FluentAssertions.Common; /// /// Defines the modes in which custom implementations of -/// are detected as configured through . +/// are detected as configured through . /// public enum ValueFormatterDetectionMode { @@ -12,13 +14,13 @@ public enum ValueFormatterDetectionMode Disabled, /// - /// Only custom value formatters exposed through the assembly set in + /// Only custom value formatters exposed through the assembly set in /// are detected. /// Specific, /// - /// All custom value formatters in any assembly loaded in the current will be detected. + /// All custom value formatters in any assembly loaded in the current AppDomain will be detected. /// Scan, } diff --git a/Src/FluentAssertions/Configuration/GlobalConfiguration.cs b/Src/FluentAssertions/Configuration/GlobalConfiguration.cs new file mode 100644 index 0000000000..1546ec4a8e --- /dev/null +++ b/Src/FluentAssertions/Configuration/GlobalConfiguration.cs @@ -0,0 +1,10 @@ +namespace FluentAssertions.Configuration; + +public class GlobalConfiguration +{ + public GlobalFormattingOptions Formatting { get; set; } = new(); + + public GlobalEquivalencyOptions Equivalency { get; set; } = new(); + + public string TestFrameworkName { get; set; } +} diff --git a/Src/FluentAssertions/AssertionOptions.cs b/Src/FluentAssertions/Configuration/GlobalEquivalencyOptions.cs similarity index 50% rename from Src/FluentAssertions/AssertionOptions.cs rename to Src/FluentAssertions/Configuration/GlobalEquivalencyOptions.cs index 3aa20c1658..1dbf8efb10 100644 --- a/Src/FluentAssertions/AssertionOptions.cs +++ b/Src/FluentAssertions/Configuration/GlobalEquivalencyOptions.cs @@ -1,29 +1,22 @@ -using System; +using System; using FluentAssertions.Common; using FluentAssertions.Equivalency; -using FluentAssertions.Formatting; -namespace FluentAssertions; +namespace FluentAssertions.Configuration; -/// -/// Holds any global options that control the behavior of FluentAssertions. -/// -public static class AssertionOptions +public class GlobalEquivalencyOptions { - private static EquivalencyOptions defaults = new(); - - static AssertionOptions() - { - EquivalencyPlan = new EquivalencyPlan(); - } + private EquivalencyOptions defaults = new(); /// - /// Creates a clone of the default options and allows the caller to modify them. + /// Represents a mutable plan consisting of steps that are executed while asserting a (collection of) object(s) + /// is structurally equivalent to another (collection of) object(s). /// - public static EquivalencyOptions CloneDefaults() - { - return new EquivalencyOptions(defaults); - } + /// + /// Members on this property are not thread-safe and should not be invoked from within a unit test. + /// See the docs on how to safely use it. + /// + public EquivalencyPlan Plan { get; } = new(); /// /// Allows configuring the defaults used during a structural equivalency assertion. @@ -32,34 +25,22 @@ public static EquivalencyOptions CloneDefaults() /// This method is not thread-safe and should not be invoked from within a unit test. /// See the docs on how to safely use it. /// - /// + /// /// An action that is used to configure the defaults. /// - /// is . - public static void AssertEquivalencyUsing( - Func defaultsConfigurer) + /// is . + public void Modify(Func configureOptions) { - Guard.ThrowIfArgumentIsNull(defaultsConfigurer); + Guard.ThrowIfArgumentIsNull(configureOptions); - defaults = defaultsConfigurer(defaults); + defaults = configureOptions(defaults); } /// - /// Represents a mutable plan consisting of steps that are executed while asserting a (collection of) object(s) - /// is structurally equivalent to another (collection of) object(s). - /// - /// - /// Members on this property are not thread-safe and should not be invoked from within a unit test. - /// See the docs on how to safely use it. - /// - public static EquivalencyPlan EquivalencyPlan { get; } - - /// - /// Gets the default formatting options used by the formatters in Fluent Assertions. + /// Creates a clone of the default options and allows the caller to modify them. /// - /// - /// Members on this property should not be invoked from within a unit test. - /// See the docs on how to safely use it. - /// - public static FormattingOptions FormattingOptions { get; } = new(); + internal EquivalencyOptions CloneDefaults() + { + return new EquivalencyOptions(defaults); + } } diff --git a/Src/FluentAssertions/Configuration/GlobalFormattingOptions.cs b/Src/FluentAssertions/Configuration/GlobalFormattingOptions.cs new file mode 100644 index 0000000000..b52267b614 --- /dev/null +++ b/Src/FluentAssertions/Configuration/GlobalFormattingOptions.cs @@ -0,0 +1,11 @@ +using FluentAssertions.Common; +using FluentAssertions.Formatting; + +namespace FluentAssertions.Configuration; + +public class GlobalFormattingOptions : FormattingOptions +{ + public string ValueFormatterAssembly { get; set; } + + public ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } +} diff --git a/Src/FluentAssertions/EquivalencyPlan.cs b/Src/FluentAssertions/Equivalency/EquivalencyPlan.cs similarity index 82% rename from Src/FluentAssertions/EquivalencyPlan.cs rename to Src/FluentAssertions/Equivalency/EquivalencyPlan.cs index 13ee80b49c..3d1c4b7c4e 100644 --- a/Src/FluentAssertions/EquivalencyPlan.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyPlan.cs @@ -4,12 +4,11 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using FluentAssertions.Equivalency; using FluentAssertions.Equivalency.Steps; #endregion -namespace FluentAssertions; +namespace FluentAssertions.Equivalency; /// /// Represents a mutable collection of equivalency steps that can be reordered and/or amended with additional @@ -34,7 +33,7 @@ IEnumerator IEnumerable.GetEnumerator() /// . /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void Add() @@ -47,7 +46,7 @@ public void Add() /// Adds a new right after the specified . /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void AddAfter() @@ -69,7 +68,7 @@ public void AddAfter() /// Inserts a new before any of the built-in steps. /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void Insert() @@ -82,7 +81,7 @@ public void Insert() /// Inserts a new just before the . /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void InsertBefore() @@ -104,7 +103,7 @@ public void InsertBefore() /// Removes all instances of the specified from the current step. /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void Remove() @@ -117,7 +116,7 @@ public void Remove() /// Removes each and every built-in . /// /// - /// This method is not thread-safe and should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void Clear() @@ -129,7 +128,7 @@ public void Clear() /// Removes all custom s. /// /// - /// This method should not be invoked on from within a unit test. + /// This method is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public void Reset() diff --git a/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs b/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs index e79505f608..3e088dd0ec 100644 --- a/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs +++ b/Src/FluentAssertions/Equivalency/EquivalencyValidator.cs @@ -92,7 +92,7 @@ private void TryToProveNodesAreEquivalent(Comparands comparands, IEquivalencyVal { using var _ = context.Tracer.WriteBlock(node => node.Description); - foreach (IEquivalencyStep step in AssertionOptions.EquivalencyPlan) + foreach (IEquivalencyStep step in AssertionConfiguration.Current.Equivalency.Plan) { var result = step.Handle(comparands, context, this); diff --git a/Src/FluentAssertions/Execution/AssertionScope.cs b/Src/FluentAssertions/Execution/AssertionScope.cs index a5d6783eb6..1a3e135c05 100644 --- a/Src/FluentAssertions/Execution/AssertionScope.cs +++ b/Src/FluentAssertions/Execution/AssertionScope.cs @@ -130,7 +130,7 @@ public static AssertionScope Current /// /// Exposes the options the scope will use for formatting objects in case an assertion fails. /// - public FormattingOptions FormattingOptions { get; } = AssertionOptions.FormattingOptions.Clone(); + public FormattingOptions FormattingOptions { get; } = AssertionConfiguration.Current.Formatting.Clone(); /// /// Adds a pre-formatted failure message to the current scope. diff --git a/Src/FluentAssertions/Execution/CollectingAssertionStrategy.cs b/Src/FluentAssertions/Execution/CollectingAssertionStrategy.cs index 96b801d73f..4c017f1806 100644 --- a/Src/FluentAssertions/Execution/CollectingAssertionStrategy.cs +++ b/Src/FluentAssertions/Execution/CollectingAssertionStrategy.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.Linq; using System.Text; -using FluentAssertions.Common; namespace FluentAssertions.Execution; @@ -44,7 +43,7 @@ public void ThrowIfAny(IDictionary context) } } - Services.ThrowException(builder.ToString()); + AssertionEngine.TestFramework.Throw(builder.ToString()); } } diff --git a/Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs b/Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs index f8ae691144..2a0a5d8e03 100644 --- a/Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs +++ b/Src/FluentAssertions/Execution/DefaultAssertionStrategy.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using FluentAssertions.Common; namespace FluentAssertions.Execution; @@ -17,7 +16,7 @@ internal class DefaultAssertionStrategy : IAssertionStrategy /// public void HandleFailure(string message) { - Services.ThrowException(message); + AssertionEngine.TestFramework.Throw(message); } /// diff --git a/Src/FluentAssertions/Execution/ITestFramework.cs b/Src/FluentAssertions/Execution/ITestFramework.cs index dcbdabb840..9aa877ea26 100644 --- a/Src/FluentAssertions/Execution/ITestFramework.cs +++ b/Src/FluentAssertions/Execution/ITestFramework.cs @@ -5,7 +5,7 @@ namespace FluentAssertions.Execution; /// /// Represents an abstraction of a particular test framework such as MSTest, nUnit, etc. /// -internal interface ITestFramework +public interface ITestFramework { /// /// Gets a value indicating whether the corresponding test framework is currently available. diff --git a/Src/FluentAssertions/Execution/TestFrameworkProvider.cs b/Src/FluentAssertions/Execution/TestFrameworkFactory.cs similarity index 68% rename from Src/FluentAssertions/Execution/TestFrameworkProvider.cs rename to Src/FluentAssertions/Execution/TestFrameworkFactory.cs index 9710e23af4..5dd3d52b3a 100644 --- a/Src/FluentAssertions/Execution/TestFrameworkProvider.cs +++ b/Src/FluentAssertions/Execution/TestFrameworkFactory.cs @@ -1,18 +1,16 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using FluentAssertions.Common; namespace FluentAssertions.Execution; /// -/// Implements a wrapper around all supported test frameworks to throw the correct assertion exception. +/// Determines the test framework, either by scanning the current app domain for known test framework assemblies or by +/// passing the framework name directly. /// -internal class TestFrameworkProvider +internal static class TestFrameworkFactory { - #region Private Definitions - private static readonly Dictionary Frameworks = new(StringComparer.OrdinalIgnoreCase) { ["mspec"] = new MSpecFramework(), @@ -25,37 +23,24 @@ internal class TestFrameworkProvider ["xunit3"] = new XUnitTestFramework("xunit.v3.assert"), }; - private readonly Configuration configuration; - - private ITestFramework testFramework; - - #endregion - - public TestFrameworkProvider(Configuration configuration) +#pragma warning disable AV1553 + public static ITestFramework GetFramework(string testFrameWorkName = null) +#pragma warning restore AV1553 { - this.configuration = configuration; - } + ITestFramework framework = null; - [DoesNotReturn] - public void Throw(string message) - { - testFramework ??= DetectFramework(); - testFramework.Throw(message); - } + if (!testFrameWorkName.IsNullOrEmpty()) + { + framework = AttemptToDetectUsingSetting(testFrameWorkName); + } - private ITestFramework DetectFramework() - { - ITestFramework detectedFramework = AttemptToDetectUsingAppSetting() - ?? AttemptToDetectUsingDynamicScanning() - ?? new FallbackTestFramework(); + framework ??= AttemptToDetectUsingDynamicScanning(); - return detectedFramework; + return framework ?? new FallbackTestFramework(); } - private ITestFramework AttemptToDetectUsingAppSetting() + private static ITestFramework AttemptToDetectUsingSetting(string frameworkName) { - string frameworkName = configuration.TestFrameworkName; - if (string.IsNullOrEmpty(frameworkName)) { return null; diff --git a/Src/FluentAssertions/Formatting/AttributeBasedFormatter.cs b/Src/FluentAssertions/Formatting/AttributeBasedFormatter.cs index 430b8241d1..8fc67bf751 100644 --- a/Src/FluentAssertions/Formatting/AttributeBasedFormatter.cs +++ b/Src/FluentAssertions/Formatting/AttributeBasedFormatter.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Reflection; using FluentAssertions.Common; +using FluentAssertions.Configuration; namespace FluentAssertions.Formatting; @@ -13,7 +14,7 @@ namespace FluentAssertions.Formatting; public class AttributeBasedFormatter : IValueFormatter { private Dictionary formatters; - private ValueFormatterDetectionMode detectionMode = ValueFormatterDetectionMode.Disabled; + private ValueFormatterDetectionMode detectionMode; /// /// Indicates whether the current can handle the specified . @@ -27,10 +28,7 @@ public bool CanHandle(object value) return IsScanningEnabled && value is not null && GetFormatter(value) is not null; } - private static bool IsScanningEnabled - { - get { return Configuration.Current.ValueFormatterDetectionMode != ValueFormatterDetectionMode.Disabled; } - } + private static bool IsScanningEnabled => AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode == ValueFormatterDetectionMode.Scan; public void Format(object value, FormattedObjectGraph formattedGraph, FormattingContext context, FormatChild formatChild) { @@ -71,17 +69,20 @@ private Dictionary Formatters private void HandleValueFormatterDetectionModeChanges() { - if (detectionMode != Configuration.Current.ValueFormatterDetectionMode) + ValueFormatterDetectionMode configuredDetectionMode = + AssertionEngine.Configuration.Formatting.ValueFormatterDetectionMode; + + if (detectionMode != configuredDetectionMode) { - detectionMode = Configuration.Current.ValueFormatterDetectionMode; + detectionMode = configuredDetectionMode; formatters = null; } } - private static Dictionary FindCustomFormatters() + private Dictionary FindCustomFormatters() { var query = - from type in Services.Reflector.GetAllTypesFromAppDomain(Applicable) + from type in TypeReflector.GetAllTypesFromAppDomain(Applicable) where type is not null from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public) where method.IsStatic @@ -98,13 +99,13 @@ into formatterGroup return query.ToDictionary(f => f.Type, f => f.Method); } - private static bool Applicable(Assembly assembly) + private bool Applicable(Assembly assembly) { - Configuration configuration = Configuration.Current; - ValueFormatterDetectionMode mode = configuration.ValueFormatterDetectionMode; + GlobalFormattingOptions options1 = AssertionEngine.Configuration.Formatting; + ValueFormatterDetectionMode mode = options1.ValueFormatterDetectionMode; return mode == ValueFormatterDetectionMode.Scan || ( mode == ValueFormatterDetectionMode.Specific && - assembly.FullName.Split(',')[0].Equals(configuration.ValueFormatterAssembly, StringComparison.OrdinalIgnoreCase)); + assembly.FullName.Split(',')[0].Equals(options1.ValueFormatterAssembly, StringComparison.OrdinalIgnoreCase)); } } diff --git a/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs b/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs index e86f2185b4..91e8d421bf 100644 --- a/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs +++ b/Src/FluentAssertions/Formatting/FormattedObjectGraph.cs @@ -117,7 +117,7 @@ private void AppendWithoutExceedingMaximumLines(string line) lines.Add( $"(Output has exceeded the maximum of {maxLines} lines. " + - $"Increase {nameof(FormattingOptions)}.{nameof(FormattingOptions.MaxLines)} on {nameof(AssertionScope)} or {nameof(AssertionOptions)} to include more lines.)"); + $"Increase {nameof(FormattingOptions)}.{nameof(FormattingOptions.MaxLines)} on {nameof(AssertionScope)} or {nameof(AssertionConfiguration)} to include more lines.)"); throw new MaxLinesExceededException(); } diff --git a/Src/FluentAssertions/Formatting/Formatter.cs b/Src/FluentAssertions/Formatting/Formatter.cs index c8683ce37c..8b1d75d0f1 100644 --- a/Src/FluentAssertions/Formatting/Formatter.cs +++ b/Src/FluentAssertions/Formatting/Formatter.cs @@ -140,7 +140,7 @@ private static void FormatChild(string path, object value, FormattedObjectGraph else if (graph.Depth > options.MaxDepth) { output.AddLine($"Maximum recursion depth of {options.MaxDepth} was reached. " + - $" Increase {nameof(FormattingOptions.MaxDepth)} on {nameof(AssertionScope)} or {nameof(AssertionOptions)} to get more details."); + $" Increase {nameof(FormattingOptions.MaxDepth)} on {nameof(AssertionScope)} or {nameof(AssertionConfiguration)} to get more details."); } else { diff --git a/Src/FluentAssertions/Formatting/FormattingOptions.cs b/Src/FluentAssertions/Formatting/FormattingOptions.cs index 769baf610f..3556adb4b8 100644 --- a/Src/FluentAssertions/Formatting/FormattingOptions.cs +++ b/Src/FluentAssertions/Formatting/FormattingOptions.cs @@ -11,7 +11,7 @@ public class FormattingOptions /// Indicates whether the formatter should use line breaks when the supports it. /// /// - /// This value should not be changed on from within a unit test. + /// This property is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// public bool UseLineBreaks { get; set; } @@ -20,7 +20,7 @@ public class FormattingOptions /// Determines the depth until which the library should try to render an object graph. /// /// - /// This value should not be changed on from within a unit test. + /// This property is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// /// @@ -36,7 +36,7 @@ public class FormattingOptions /// Because of technical reasons, the actual output may be one or two lines longer. /// /// - /// This value should not be changed on from within a unit test. + /// This property is not thread-safe and should not be modified through from within a unit test. /// See the docs on how to safely use it. /// /// diff --git a/Src/FluentAssertions/Numeric/ComparableTypeAssertions.cs b/Src/FluentAssertions/Numeric/ComparableTypeAssertions.cs index 3a40a75be1..919f944d8e 100644 --- a/Src/FluentAssertions/Numeric/ComparableTypeAssertions.cs +++ b/Src/FluentAssertions/Numeric/ComparableTypeAssertions.cs @@ -67,7 +67,7 @@ public AndConstraint Be(T expected, [StringSyntax("CompositeFormat" /// /// Objects are equivalent when both object graphs have equally named properties with the same value, /// irrespective of the type of those objects. Two properties are also equal if one type can be converted to another and the result is equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// The expected element. /// @@ -95,7 +95,7 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the /// class. The global defaults are determined by the - /// class. + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -111,7 +111,7 @@ public AndConstraint BeEquivalentTo(TExpectation expe { Guard.ThrowIfArgumentIsNull(config); - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var context = new EquivalencyValidationContext( Node.From(() => CurrentAssertionChain.CallerIdentifier), options) diff --git a/Src/FluentAssertions/ObjectAssertionsExtensions.cs b/Src/FluentAssertions/ObjectAssertionsExtensions.cs index 87281fdc78..72b8cf5764 100644 --- a/Src/FluentAssertions/ObjectAssertionsExtensions.cs +++ b/Src/FluentAssertions/ObjectAssertionsExtensions.cs @@ -38,7 +38,7 @@ public static AndConstraint BeDataContractSerializable(this Ob /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the /// class. The global defaults are determined by the - /// class. + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -58,7 +58,7 @@ public static AndConstraint BeDataContractSerializable(this { var deserializedObject = CreateCloneUsingDataContractSerializer(assertions.Subject); - EquivalencyOptions defaultOptions = AssertionOptions.CloneDefaults() + EquivalencyOptions defaultOptions = AssertionConfiguration.Current.Equivalency.CloneDefaults() .PreferringRuntimeMemberTypes().IncludingFields().IncludingProperties(); ((T)deserializedObject).Should().BeEquivalentTo((T)assertions.Subject, _ => options(defaultOptions)); diff --git a/Src/FluentAssertions/Primitives/ObjectAssertions.cs b/Src/FluentAssertions/Primitives/ObjectAssertions.cs index 9c536c3d66..f75f84120c 100644 --- a/Src/FluentAssertions/Primitives/ObjectAssertions.cs +++ b/Src/FluentAssertions/Primitives/ObjectAssertions.cs @@ -243,7 +243,7 @@ public AndConstraint NotBe(TSubject unexpected, IEqualityComparer and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// The expected element. /// @@ -273,7 +273,7 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the /// class. The global defaults are determined by the - /// class. + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion @@ -289,7 +289,7 @@ public AndConstraint BeEquivalentTo(TExpectation expe { Guard.ThrowIfArgumentIsNull(config); - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var context = new EquivalencyValidationContext(Node.From(() => CurrentAssertionChain.CallerIdentifier), options) @@ -318,7 +318,7 @@ public AndConstraint BeEquivalentTo(TExpectation expe /// irrespective of the type of those objects. Two properties are also equal if one type can be converted to another and the result is equal. /// The type of a collection property is ignored as long as the collection implements and all /// items in the collection are structurally equal. - /// Notice that actual behavior is determined by the global defaults managed by . + /// Notice that actual behavior is determined by the global defaults managed by . /// /// The unexpected element. /// @@ -349,7 +349,7 @@ public AndConstraint NotBeEquivalentTo( /// A reference to the configuration object that can be used /// to influence the way the object graphs are compared. You can also provide an alternative instance of the /// class. The global defaults are determined by the - /// class. + /// class. /// /// /// A formatted phrase as is supported by explaining why the assertion diff --git a/Src/FluentAssertions/Primitives/StringAssertions.cs b/Src/FluentAssertions/Primitives/StringAssertions.cs index 7e1fac4730..d114002e9b 100644 --- a/Src/FluentAssertions/Primitives/StringAssertions.cs +++ b/Src/FluentAssertions/Primitives/StringAssertions.cs @@ -150,7 +150,7 @@ public AndConstraint BeEquivalentTo(string expected, { Guard.ThrowIfArgumentIsNull(config); - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var expectation = new StringValidator(assertionChain, new StringEqualityStrategy(options.GetStringComparerOrDefault(), "be equivalent to"), @@ -473,7 +473,7 @@ public AndConstraint MatchEquivalentOf(string wildcardPattern, Guard.ThrowIfArgumentIsNull(config); - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var stringWildcardMatchingValidator = new StringValidator(assertionChain, new StringWildcardMatchingStrategy @@ -598,7 +598,7 @@ public AndConstraint NotMatchEquivalentOf(string wildcardPattern, Guard.ThrowIfArgumentIsNull(config); - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var stringWildcardMatchingValidator = new StringValidator(assertionChain, new StringWildcardMatchingStrategy @@ -987,7 +987,7 @@ public AndConstraint StartWithEquivalentOf(string expected, Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare string start equivalence with ."); Guard.ThrowIfArgumentIsNull(config); - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var stringStartValidator = new StringValidator(assertionChain, new StringStartStrategy(options.GetStringComparerOrDefault(), "start with equivalent of"), @@ -1182,7 +1182,7 @@ public AndConstraint EndWithEquivalentOf(string expected, Guard.ThrowIfArgumentIsNull(expected, nameof(expected), "Cannot compare string end equivalence with ."); Guard.ThrowIfArgumentIsNull(config); - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var stringEndValidator = new StringValidator(assertionChain, new StringEndStrategy(options.GetStringComparerOrDefault(), "end with equivalent of"), @@ -1419,7 +1419,7 @@ public AndConstraint ContainEquivalentOf(string expected, Guard.ThrowIfArgumentIsNull(occurrenceConstraint); Guard.ThrowIfArgumentIsNull(config); - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var stringContainValidator = new StringValidatorSupportingNull(assertionChain, new StringContainsStrategy(options.GetStringComparerOrDefault(), occurrenceConstraint), @@ -2009,7 +2009,7 @@ internal AndConstraint Be(string expected, { Guard.ThrowIfArgumentIsNull(config); - EquivalencyOptions options = config(AssertionOptions.CloneDefaults()); + EquivalencyOptions options = config(AssertionConfiguration.Current.Equivalency.CloneDefaults()); var expectation = new StringValidator(assertionChain, new StringEqualityStrategy(options.GetStringComparerOrDefault(), "be"), diff --git a/Src/FluentAssertions/Specialized/ExceptionAssertions.cs b/Src/FluentAssertions/Specialized/ExceptionAssertions.cs index ceaa98ec85..a92e40a8ae 100644 --- a/Src/FluentAssertions/Specialized/ExceptionAssertions.cs +++ b/Src/FluentAssertions/Specialized/ExceptionAssertions.cs @@ -246,7 +246,7 @@ private TException SingleSubject { string thrownExceptions = BuildExceptionsString(Subject); - Services.ThrowException( + AssertionEngine.TestFramework.Throw( $"More than one exception was thrown. FluentAssertions cannot determine which Exception was meant.{Environment.NewLine}{thrownExceptions}"); } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt index 519a0bbd64..23f1d8c67e 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net47.verified.txt @@ -24,6 +24,16 @@ namespace FluentAssertions public TSubject Subject { get; } public TSubject Which { get; } } + public static class AssertionConfiguration + { + public static FluentAssertions.Configuration.GlobalConfiguration Current { get; } + } + public static class AssertionEngine + { + public static FluentAssertions.Configuration.GlobalConfiguration Configuration { get; } + public static FluentAssertions.Execution.ITestFramework TestFramework { get; set; } + public static void ResetToDefaults() { } + } public static class AssertionExtensions { public static TTo As(this object subject) { } @@ -160,13 +170,6 @@ namespace FluentAssertions public static FluentAssertions.Collections.GenericDictionaryAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TCollection actualValue) where TCollection : System.Collections.Generic.IEnumerable> { } } - public static class AssertionOptions - { - public static FluentAssertions.EquivalencyPlan EquivalencyPlan { get; } - public static FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public static void AssertEquivalencyUsing(System.Func defaultsConfigurer) { } - public static FluentAssertions.Equivalency.EquivalencyOptions CloneDefaults() { } - } public static class AsyncAssertionsExtensions { public static System.Threading.Tasks.Task, T>> WithResult(this System.Threading.Tasks.Task, T>> task, T expected, string because = "", params object[] becauseArgs) { } @@ -208,23 +211,6 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableEnumAssertions Should(this TEnum? @enum) where TEnum : struct, System.Enum { } } - public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable - { - public EquivalencyPlan() { } - public void Add() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void AddAfter() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Clear() { } - public System.Collections.Generic.IEnumerator GetEnumerator() { } - public void Insert() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void InsertBefore() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Remove() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } - public void Reset() { } - } public static class EventRaisingExtensions { public static FluentAssertions.Events.IEventRecording WithArgs(this FluentAssertions.Events.IEventRecording eventRecording, System.Linq.Expressions.Expression> predicate) { } @@ -581,14 +567,6 @@ namespace FluentAssertions.Common InvalidForCSharp = 5, PrivateProtected = 6, } - public class Configuration - { - public Configuration(FluentAssertions.Common.IConfigurationStore store) { } - public string TestFrameworkName { get; set; } - public string ValueFormatterAssembly { get; set; } - public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } - public static FluentAssertions.Common.Configuration Current { get; } - } public static class DateTimeExtensions { public static System.DateTimeOffset ToDateTimeOffset(this System.DateTime dateTime) { } @@ -600,26 +578,10 @@ namespace FluentAssertions.Common System.Threading.Tasks.Task DelayAsync(System.TimeSpan delay, System.Threading.CancellationToken cancellationToken); FluentAssertions.Common.ITimer StartTimer(); } - public interface IConfigurationStore - { - string GetSetting(string name); - } - public interface IReflector - { - System.Collections.Generic.IEnumerable GetAllTypesFromAppDomain(System.Func predicate); - } public interface ITimer : System.IDisposable { System.TimeSpan Elapsed { get; } } - public static class Services - { - public static FluentAssertions.Common.Configuration Configuration { get; } - public static FluentAssertions.Common.IConfigurationStore ConfigurationStore { get; set; } - public static FluentAssertions.Common.IReflector Reflector { get; set; } - public static System.Action ThrowException { get; set; } - public static void ResetToDefaults() { } - } public delegate FluentAssertions.Common.ITimer StartTimer(); public enum ValueFormatterDetectionMode { @@ -628,6 +590,28 @@ namespace FluentAssertions.Common Scan = 2, } } +namespace FluentAssertions.Configuration +{ + public class GlobalConfiguration + { + public GlobalConfiguration() { } + public FluentAssertions.Configuration.GlobalEquivalencyOptions Equivalency { get; set; } + public FluentAssertions.Configuration.GlobalFormattingOptions Formatting { get; set; } + public string TestFrameworkName { get; set; } + } + public class GlobalEquivalencyOptions + { + public GlobalEquivalencyOptions() { } + public FluentAssertions.Equivalency.EquivalencyPlan Plan { get; } + public void Modify(System.Func configureOptions) { } + } + public class GlobalFormattingOptions : FluentAssertions.Formatting.FormattingOptions + { + public GlobalFormattingOptions() { } + public string ValueFormatterAssembly { get; set; } + public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } + } +} namespace FluentAssertions.Equivalency { public class Comparands @@ -687,6 +671,23 @@ namespace FluentAssertions.Equivalency public FluentAssertions.Equivalency.EquivalencyOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.EquivalencyOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } } + public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + { + public EquivalencyPlan() { } + public void Add() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void AddAfter() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Clear() { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Insert() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void InsertBefore() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Remove() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } + public void Reset() { } + } public enum EquivalencyResult { ContinueWithNext = 0, @@ -1187,6 +1188,12 @@ namespace FluentAssertions.Execution { object Clone(); } + public interface ITestFramework + { + bool IsAvailable { get; } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] + void Throw(string message); + } public class Reason { public Reason(string formattedMessage, object[] arguments) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt index 7f1a4b1739..908e22f165 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/net6.0.verified.txt @@ -24,6 +24,16 @@ namespace FluentAssertions public TSubject Subject { get; } public TSubject Which { get; } } + public static class AssertionConfiguration + { + public static FluentAssertions.Configuration.GlobalConfiguration Current { get; } + } + public static class AssertionEngine + { + public static FluentAssertions.Configuration.GlobalConfiguration Configuration { get; } + public static FluentAssertions.Execution.ITestFramework TestFramework { get; set; } + public static void ResetToDefaults() { } + } public static class AssertionExtensions { public static TTo As(this object subject) { } @@ -173,13 +183,6 @@ namespace FluentAssertions public static FluentAssertions.Collections.GenericDictionaryAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TCollection actualValue) where TCollection : System.Collections.Generic.IEnumerable> { } } - public static class AssertionOptions - { - public static FluentAssertions.EquivalencyPlan EquivalencyPlan { get; } - public static FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public static void AssertEquivalencyUsing(System.Func defaultsConfigurer) { } - public static FluentAssertions.Equivalency.EquivalencyOptions CloneDefaults() { } - } public static class AsyncAssertionsExtensions { public static System.Threading.Tasks.Task, T>> WithResult(this System.Threading.Tasks.Task, T>> task, T expected, string because = "", params object[] becauseArgs) { } @@ -221,23 +224,6 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableEnumAssertions Should(this TEnum? @enum) where TEnum : struct, System.Enum { } } - public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable - { - public EquivalencyPlan() { } - public void Add() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void AddAfter() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Clear() { } - public System.Collections.Generic.IEnumerator GetEnumerator() { } - public void Insert() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void InsertBefore() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Remove() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } - public void Reset() { } - } public static class EventRaisingExtensions { public static FluentAssertions.Events.IEventRecording WithArgs(this FluentAssertions.Events.IEventRecording eventRecording, System.Linq.Expressions.Expression> predicate) { } @@ -594,14 +580,6 @@ namespace FluentAssertions.Common InvalidForCSharp = 5, PrivateProtected = 6, } - public class Configuration - { - public Configuration(FluentAssertions.Common.IConfigurationStore store) { } - public string TestFrameworkName { get; set; } - public string ValueFormatterAssembly { get; set; } - public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } - public static FluentAssertions.Common.Configuration Current { get; } - } public static class DateTimeExtensions { public static System.DateTimeOffset ToDateTimeOffset(this System.DateTime dateTime) { } @@ -613,26 +591,10 @@ namespace FluentAssertions.Common System.Threading.Tasks.Task DelayAsync(System.TimeSpan delay, System.Threading.CancellationToken cancellationToken); FluentAssertions.Common.ITimer StartTimer(); } - public interface IConfigurationStore - { - string GetSetting(string name); - } - public interface IReflector - { - System.Collections.Generic.IEnumerable GetAllTypesFromAppDomain(System.Func predicate); - } public interface ITimer : System.IDisposable { System.TimeSpan Elapsed { get; } } - public static class Services - { - public static FluentAssertions.Common.Configuration Configuration { get; } - public static FluentAssertions.Common.IConfigurationStore ConfigurationStore { get; set; } - public static FluentAssertions.Common.IReflector Reflector { get; set; } - public static System.Action ThrowException { get; set; } - public static void ResetToDefaults() { } - } public delegate FluentAssertions.Common.ITimer StartTimer(); public enum ValueFormatterDetectionMode { @@ -641,6 +603,28 @@ namespace FluentAssertions.Common Scan = 2, } } +namespace FluentAssertions.Configuration +{ + public class GlobalConfiguration + { + public GlobalConfiguration() { } + public FluentAssertions.Configuration.GlobalEquivalencyOptions Equivalency { get; set; } + public FluentAssertions.Configuration.GlobalFormattingOptions Formatting { get; set; } + public string TestFrameworkName { get; set; } + } + public class GlobalEquivalencyOptions + { + public GlobalEquivalencyOptions() { } + public FluentAssertions.Equivalency.EquivalencyPlan Plan { get; } + public void Modify(System.Func configureOptions) { } + } + public class GlobalFormattingOptions : FluentAssertions.Formatting.FormattingOptions + { + public GlobalFormattingOptions() { } + public string ValueFormatterAssembly { get; set; } + public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } + } +} namespace FluentAssertions.Equivalency { public class Comparands @@ -700,6 +684,23 @@ namespace FluentAssertions.Equivalency public FluentAssertions.Equivalency.EquivalencyOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.EquivalencyOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } } + public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + { + public EquivalencyPlan() { } + public void Add() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void AddAfter() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Clear() { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Insert() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void InsertBefore() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Remove() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } + public void Reset() { } + } public enum EquivalencyResult { ContinueWithNext = 0, @@ -1200,6 +1201,12 @@ namespace FluentAssertions.Execution { object Clone(); } + public interface ITestFramework + { + bool IsAvailable { get; } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] + void Throw(string message); + } public class Reason { public Reason(string formattedMessage, object[] arguments) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt index 08eaf7cde3..c270f41465 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.0.verified.txt @@ -24,6 +24,16 @@ namespace FluentAssertions public TSubject Subject { get; } public TSubject Which { get; } } + public static class AssertionConfiguration + { + public static FluentAssertions.Configuration.GlobalConfiguration Current { get; } + } + public static class AssertionEngine + { + public static FluentAssertions.Configuration.GlobalConfiguration Configuration { get; } + public static FluentAssertions.Execution.ITestFramework TestFramework { get; set; } + public static void ResetToDefaults() { } + } public static class AssertionExtensions { public static TTo As(this object subject) { } @@ -158,13 +168,6 @@ namespace FluentAssertions public static FluentAssertions.Collections.GenericDictionaryAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TCollection actualValue) where TCollection : System.Collections.Generic.IEnumerable> { } } - public static class AssertionOptions - { - public static FluentAssertions.EquivalencyPlan EquivalencyPlan { get; } - public static FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public static void AssertEquivalencyUsing(System.Func defaultsConfigurer) { } - public static FluentAssertions.Equivalency.EquivalencyOptions CloneDefaults() { } - } public static class AsyncAssertionsExtensions { public static System.Threading.Tasks.Task, T>> WithResult(this System.Threading.Tasks.Task, T>> task, T expected, string because = "", params object[] becauseArgs) { } @@ -206,23 +209,6 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableEnumAssertions Should(this TEnum? @enum) where TEnum : struct, System.Enum { } } - public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable - { - public EquivalencyPlan() { } - public void Add() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void AddAfter() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Clear() { } - public System.Collections.Generic.IEnumerator GetEnumerator() { } - public void Insert() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void InsertBefore() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Remove() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } - public void Reset() { } - } public static class Exactly { public static FluentAssertions.OccurrenceConstraint Once() { } @@ -573,14 +559,6 @@ namespace FluentAssertions.Common InvalidForCSharp = 5, PrivateProtected = 6, } - public class Configuration - { - public Configuration(FluentAssertions.Common.IConfigurationStore store) { } - public string TestFrameworkName { get; set; } - public string ValueFormatterAssembly { get; set; } - public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } - public static FluentAssertions.Common.Configuration Current { get; } - } public static class DateTimeExtensions { public static System.DateTimeOffset ToDateTimeOffset(this System.DateTime dateTime) { } @@ -592,26 +570,10 @@ namespace FluentAssertions.Common System.Threading.Tasks.Task DelayAsync(System.TimeSpan delay, System.Threading.CancellationToken cancellationToken); FluentAssertions.Common.ITimer StartTimer(); } - public interface IConfigurationStore - { - string GetSetting(string name); - } - public interface IReflector - { - System.Collections.Generic.IEnumerable GetAllTypesFromAppDomain(System.Func predicate); - } public interface ITimer : System.IDisposable { System.TimeSpan Elapsed { get; } } - public static class Services - { - public static FluentAssertions.Common.Configuration Configuration { get; } - public static FluentAssertions.Common.IConfigurationStore ConfigurationStore { get; set; } - public static FluentAssertions.Common.IReflector Reflector { get; set; } - public static System.Action ThrowException { get; set; } - public static void ResetToDefaults() { } - } public delegate FluentAssertions.Common.ITimer StartTimer(); public enum ValueFormatterDetectionMode { @@ -620,6 +582,28 @@ namespace FluentAssertions.Common Scan = 2, } } +namespace FluentAssertions.Configuration +{ + public class GlobalConfiguration + { + public GlobalConfiguration() { } + public FluentAssertions.Configuration.GlobalEquivalencyOptions Equivalency { get; set; } + public FluentAssertions.Configuration.GlobalFormattingOptions Formatting { get; set; } + public string TestFrameworkName { get; set; } + } + public class GlobalEquivalencyOptions + { + public GlobalEquivalencyOptions() { } + public FluentAssertions.Equivalency.EquivalencyPlan Plan { get; } + public void Modify(System.Func configureOptions) { } + } + public class GlobalFormattingOptions : FluentAssertions.Formatting.FormattingOptions + { + public GlobalFormattingOptions() { } + public string ValueFormatterAssembly { get; set; } + public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } + } +} namespace FluentAssertions.Equivalency { public class Comparands @@ -679,6 +663,23 @@ namespace FluentAssertions.Equivalency public FluentAssertions.Equivalency.EquivalencyOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.EquivalencyOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } } + public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + { + public EquivalencyPlan() { } + public void Add() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void AddAfter() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Clear() { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Insert() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void InsertBefore() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Remove() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } + public void Reset() { } + } public enum EquivalencyResult { ContinueWithNext = 0, @@ -1131,6 +1132,12 @@ namespace FluentAssertions.Execution { object Clone(); } + public interface ITestFramework + { + bool IsAvailable { get; } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] + void Throw(string message); + } public class Reason { public Reason(string formattedMessage, object[] arguments) { } diff --git a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt index 5e103b1362..ec301bf8ef 100644 --- a/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt +++ b/Tests/Approval.Tests/ApprovedApi/FluentAssertions/netstandard2.1.verified.txt @@ -24,6 +24,16 @@ namespace FluentAssertions public TSubject Subject { get; } public TSubject Which { get; } } + public static class AssertionConfiguration + { + public static FluentAssertions.Configuration.GlobalConfiguration Current { get; } + } + public static class AssertionEngine + { + public static FluentAssertions.Configuration.GlobalConfiguration Configuration { get; } + public static FluentAssertions.Execution.ITestFramework TestFramework { get; set; } + public static void ResetToDefaults() { } + } public static class AssertionExtensions { public static TTo As(this object subject) { } @@ -160,13 +170,6 @@ namespace FluentAssertions public static FluentAssertions.Collections.GenericDictionaryAssertions Should([System.Diagnostics.CodeAnalysis.NotNull] this TCollection actualValue) where TCollection : System.Collections.Generic.IEnumerable> { } } - public static class AssertionOptions - { - public static FluentAssertions.EquivalencyPlan EquivalencyPlan { get; } - public static FluentAssertions.Formatting.FormattingOptions FormattingOptions { get; } - public static void AssertEquivalencyUsing(System.Func defaultsConfigurer) { } - public static FluentAssertions.Equivalency.EquivalencyOptions CloneDefaults() { } - } public static class AsyncAssertionsExtensions { public static System.Threading.Tasks.Task, T>> WithResult(this System.Threading.Tasks.Task, T>> task, T expected, string because = "", params object[] becauseArgs) { } @@ -208,23 +211,6 @@ namespace FluentAssertions public static FluentAssertions.Primitives.NullableEnumAssertions Should(this TEnum? @enum) where TEnum : struct, System.Enum { } } - public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable - { - public EquivalencyPlan() { } - public void Add() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void AddAfter() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Clear() { } - public System.Collections.Generic.IEnumerator GetEnumerator() { } - public void Insert() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void InsertBefore() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } - public void Remove() - where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } - public void Reset() { } - } public static class EventRaisingExtensions { public static FluentAssertions.Events.IEventRecording WithArgs(this FluentAssertions.Events.IEventRecording eventRecording, System.Linq.Expressions.Expression> predicate) { } @@ -581,14 +567,6 @@ namespace FluentAssertions.Common InvalidForCSharp = 5, PrivateProtected = 6, } - public class Configuration - { - public Configuration(FluentAssertions.Common.IConfigurationStore store) { } - public string TestFrameworkName { get; set; } - public string ValueFormatterAssembly { get; set; } - public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } - public static FluentAssertions.Common.Configuration Current { get; } - } public static class DateTimeExtensions { public static System.DateTimeOffset ToDateTimeOffset(this System.DateTime dateTime) { } @@ -600,26 +578,10 @@ namespace FluentAssertions.Common System.Threading.Tasks.Task DelayAsync(System.TimeSpan delay, System.Threading.CancellationToken cancellationToken); FluentAssertions.Common.ITimer StartTimer(); } - public interface IConfigurationStore - { - string GetSetting(string name); - } - public interface IReflector - { - System.Collections.Generic.IEnumerable GetAllTypesFromAppDomain(System.Func predicate); - } public interface ITimer : System.IDisposable { System.TimeSpan Elapsed { get; } } - public static class Services - { - public static FluentAssertions.Common.Configuration Configuration { get; } - public static FluentAssertions.Common.IConfigurationStore ConfigurationStore { get; set; } - public static FluentAssertions.Common.IReflector Reflector { get; set; } - public static System.Action ThrowException { get; set; } - public static void ResetToDefaults() { } - } public delegate FluentAssertions.Common.ITimer StartTimer(); public enum ValueFormatterDetectionMode { @@ -628,6 +590,28 @@ namespace FluentAssertions.Common Scan = 2, } } +namespace FluentAssertions.Configuration +{ + public class GlobalConfiguration + { + public GlobalConfiguration() { } + public FluentAssertions.Configuration.GlobalEquivalencyOptions Equivalency { get; set; } + public FluentAssertions.Configuration.GlobalFormattingOptions Formatting { get; set; } + public string TestFrameworkName { get; set; } + } + public class GlobalEquivalencyOptions + { + public GlobalEquivalencyOptions() { } + public FluentAssertions.Equivalency.EquivalencyPlan Plan { get; } + public void Modify(System.Func configureOptions) { } + } + public class GlobalFormattingOptions : FluentAssertions.Formatting.FormattingOptions + { + public GlobalFormattingOptions() { } + public string ValueFormatterAssembly { get; set; } + public FluentAssertions.Common.ValueFormatterDetectionMode ValueFormatterDetectionMode { get; set; } + } +} namespace FluentAssertions.Equivalency { public class Comparands @@ -687,6 +671,23 @@ namespace FluentAssertions.Equivalency public FluentAssertions.Equivalency.EquivalencyOptions WithStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } public FluentAssertions.Equivalency.EquivalencyOptions WithoutStrictOrderingFor(System.Linq.Expressions.Expression> expression) { } } + public class EquivalencyPlan : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable + { + public EquivalencyPlan() { } + public void Add() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void AddAfter() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Clear() { } + public System.Collections.Generic.IEnumerator GetEnumerator() { } + public void Insert() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void InsertBefore() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep, new () { } + public void Remove() + where TStep : FluentAssertions.Equivalency.IEquivalencyStep { } + public void Reset() { } + } public enum EquivalencyResult { ContinueWithNext = 0, @@ -1187,6 +1188,12 @@ namespace FluentAssertions.Execution { object Clone(); } + public interface ITestFramework + { + bool IsAvailable { get; } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] + void Throw(string message); + } public class Reason { public Reason(string formattedMessage, object[] arguments) { } diff --git a/Tests/FluentAssertions.Specs/Common/ConfigurationSpecs.cs b/Tests/FluentAssertions.Specs/Common/ConfigurationSpecs.cs deleted file mode 100644 index 43ed874d6d..0000000000 --- a/Tests/FluentAssertions.Specs/Common/ConfigurationSpecs.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Generic; -using FluentAssertions.Common; -using Xunit; - -namespace FluentAssertions.Specs.Common; - -public class ConfigurationSpecs -{ - [Fact] - public void Value_formatter_detection_mode_is_disabled_with_empty_store() - { - // Arrange - var store = new DummyConfigurationStore([]); - var sut = new Configuration(store); - - // Act / Assert - sut.ValueFormatterDetectionMode.Should().Be(ValueFormatterDetectionMode.Disabled); - } - - [Fact] - public void Value_formatter_detection_mode_is_specific_with_given_value_formatters_assembly() - { - // Arrange - var store = new DummyConfigurationStore(new Dictionary - { - { "valueFormattersAssembly", "foo" } - }); - - var sut = new Configuration(store); - - // Act / Assert - sut.ValueFormatterDetectionMode.Should().Be(ValueFormatterDetectionMode.Specific); - } - - [Fact] - public void Value_formatter_detection_mode_can_be_specified_in_configuration_store() - { - // Arrange - var store = new DummyConfigurationStore(new Dictionary - { - { "valueFormatters", nameof(ValueFormatterDetectionMode.Scan) } - }); - - var sut = new Configuration(store); - - // Act / Assert - sut.ValueFormatterDetectionMode.Should().Be(ValueFormatterDetectionMode.Scan); - } - - [Fact] - public void Value_formatter_detection_mode_throws_when_configured_incorrectly() - { - // Arrange - var store = new DummyConfigurationStore(new Dictionary - { - { "valueFormatters", "foo" } - }); - - var sut = new Configuration(store); - - // Act - var act = () => sut.ValueFormatterDetectionMode; - - // Assert - act.Should().Throw(); - } - - private class DummyConfigurationStore : IConfigurationStore - { - private readonly Dictionary settings; - - public DummyConfigurationStore(Dictionary settings) - { - this.settings = settings; - } - - public string GetSetting(string name) - => settings.TryGetValue(name, out var value) ? value : null; - } -} diff --git a/Tests/FluentAssertions.Specs/Configuration/AssertionConfigurationSpecs.cs b/Tests/FluentAssertions.Specs/Configuration/AssertionConfigurationSpecs.cs new file mode 100644 index 0000000000..a4b009bf8f --- /dev/null +++ b/Tests/FluentAssertions.Specs/Configuration/AssertionConfigurationSpecs.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace FluentAssertions.Specs.Configuration; + +[Collection("AssertionOptionsSpecs")] +public class AssertionConfigurationSpecs +{ + [Fact] + public void When_concurrently_accessing_the_current_configuration_no_exception_should_be_thrown() + { + // Act + Action act = () => Parallel.For( + 0, + 10000, + new ParallelOptions + { + MaxDegreeOfParallelism = 8 + }, + __ => + { + AssertionConfiguration.Current.Formatting.ValueFormatterAssembly = string.Empty; + _ = AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode; + } + ); + + // Assert + act.Should().NotThrow(); + } +} diff --git a/Tests/FluentAssertions.Specs/AssertionOptionsSpecs.cs b/Tests/FluentAssertions.Specs/Configuration/GlobalEquivalencyOptionsSpecs.cs similarity index 88% rename from Tests/FluentAssertions.Specs/AssertionOptionsSpecs.cs rename to Tests/FluentAssertions.Specs/Configuration/GlobalEquivalencyOptionsSpecs.cs index a931266d0b..54bcb92637 100644 --- a/Tests/FluentAssertions.Specs/AssertionOptionsSpecs.cs +++ b/Tests/FluentAssertions.Specs/Configuration/GlobalEquivalencyOptionsSpecs.cs @@ -7,12 +7,14 @@ using FluentAssertions.Equivalency.Steps; using FluentAssertions.Execution; using FluentAssertions.Formatting; +using JetBrains.Annotations; using Xunit; using Xunit.Sdk; -namespace FluentAssertions.Specs; +namespace FluentAssertions.Specs.Configuration; -public class AssertionOptionsSpecs +[Collection("AssertionOptionsSpecs")] +public class GlobalEquivalencyOptionsSpecs { // Due to tests that call AssertionOptions [CollectionDefinition("AssertionOptionsSpecs", DisableParallelization = true)] @@ -22,29 +24,27 @@ public abstract class Given_temporary_global_assertion_options : GivenWhenThen { protected override void Dispose(bool disposing) { - AssertionOptions.AssertEquivalencyUsing(_ => new EquivalencyOptions()); + AssertionConfiguration.Current.Equivalency.Modify(_ => new EquivalencyOptions()); base.Dispose(disposing); } } - [Collection("AssertionOptionsSpecs")] public class When_injecting_a_null_configurer : GivenSubject { public When_injecting_a_null_configurer() { - When(() => () => AssertionOptions.AssertEquivalencyUsing(defaultsConfigurer: null)); + When(() => () => AssertionConfiguration.Current.Equivalency.Modify(configureOptions: null)); } [Fact] public void It_should_throw() { Result.Should().ThrowExactly() - .WithParameterName("defaultsConfigurer"); + .WithParameterName("configureOptions"); } } - [Collection("AssertionOptionsSpecs")] public class When_concurrently_getting_equality_strategy : GivenSubject { public When_concurrently_getting_equality_strategy() @@ -68,7 +68,6 @@ public void It_should_not_throw() } } - [Collection("AssertionOptionsSpecs")] public class When_modifying_global_reference_type_settings_a_previous_assertion_should_not_have_any_effect : Given_temporary_global_assertion_options { @@ -80,7 +79,7 @@ public When_modifying_global_reference_type_settings_a_previous_assertion_should new MyValueType { Value = 1 }.Should().BeEquivalentTo(new MyValueType { Value = 2 }); }); - When(() => AssertionOptions.AssertEquivalencyUsing(o => o.ComparingByMembers())); + When(() => AssertionConfiguration.Current.Equivalency.Modify(o => o.ComparingByMembers())); } [Fact] @@ -113,7 +112,7 @@ public When_modifying_global_value_type_settings_a_previous_assertion_should_not new MyClass { Value = 1 }.Should().BeEquivalentTo(new MyClass { Value = 1 }); }); - When(() => AssertionOptions.AssertEquivalencyUsing(o => o.ComparingByValue())); + When(() => AssertionConfiguration.Current.Equivalency.Modify(o => o.ComparingByValue())); } [Fact] @@ -138,7 +137,7 @@ public When_modifying_record_settings_globally() { When(() => { - AssertionOptions.AssertEquivalencyUsing( + AssertionConfiguration.Current.Equivalency.Modify( options => options.ComparingByValue(typeof(Position))); }); } @@ -151,6 +150,7 @@ public void It_should_use_the_global_settings_for_comparing_records() private record Position { + [UsedImplicitly] private readonly int value; public Position(int value) @@ -167,7 +167,7 @@ public When_assertion_doubles_should_always_allow_small_deviations() { When(() => { - AssertionOptions.AssertEquivalencyUsing(options => options + AssertionConfiguration.Current.Equivalency.Modify(options => options .Using(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, 0.01)) .WhenTypeIs()); }); @@ -199,7 +199,7 @@ public When_local_similar_options_are_used() { When(() => { - AssertionOptions.AssertEquivalencyUsing(options => options + AssertionConfiguration.Current.Equivalency.Modify(options => options .Using(ctx => ctx.Subject.Should().BeApproximately(ctx.Expectation, 0.01)) .WhenTypeIs()); }); @@ -255,7 +255,7 @@ protected override void Dispose(bool disposing) protected static EquivalencyPlan Plan { - get { return AssertionOptions.EquivalencyPlan; } + get { return AssertionConfiguration.Current.Equivalency.Plan; } } } @@ -388,13 +388,13 @@ public class When_global_formatting_settings_are_modified : GivenWhenThen public When_global_formatting_settings_are_modified() { - Given(() => oldSettings = AssertionOptions.FormattingOptions.Clone()); + Given(() => oldSettings = AssertionConfiguration.Current.Formatting.Clone()); When(() => { - AssertionOptions.FormattingOptions.UseLineBreaks = true; - AssertionOptions.FormattingOptions.MaxDepth = 123; - AssertionOptions.FormattingOptions.MaxLines = 33; + AssertionConfiguration.Current.Formatting.UseLineBreaks = true; + AssertionConfiguration.Current.Formatting.MaxDepth = 123; + AssertionConfiguration.Current.Formatting.MaxLines = 33; }); } @@ -408,9 +408,9 @@ public void Then_the_current_assertion_scope_should_use_these_settings() protected override void Dispose(bool disposing) { - AssertionOptions.FormattingOptions.MaxDepth = oldSettings.MaxDepth; - AssertionOptions.FormattingOptions.UseLineBreaks = oldSettings.UseLineBreaks; - AssertionOptions.FormattingOptions.MaxLines = oldSettings.MaxLines; + AssertionConfiguration.Current.Formatting.MaxDepth = oldSettings.MaxDepth; + AssertionConfiguration.Current.Formatting.UseLineBreaks = oldSettings.UseLineBreaks; + AssertionConfiguration.Current.Formatting.MaxLines = oldSettings.MaxLines; base.Dispose(disposing); } diff --git a/Tests/FluentAssertions.Specs/Configuration/TestFrameworkProviderSpecs.cs b/Tests/FluentAssertions.Specs/Configuration/TestFrameworkProviderSpecs.cs new file mode 100644 index 0000000000..5b5a9bfd72 --- /dev/null +++ b/Tests/FluentAssertions.Specs/Configuration/TestFrameworkProviderSpecs.cs @@ -0,0 +1,56 @@ +using System; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Sdk; + +namespace FluentAssertions.Specs.Configuration; + +public class TestFrameworkProviderSpecs +{ + [Fact] + public void When_running_xunit_test_implicitly_it_should_be_detected() + { + // Arrange + var testFramework = TestFrameworkFactory.GetFramework(); + + // Act + Action act = () => testFramework.Throw("MyMessage"); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_running_xunit_test_explicitly_it_should_be_detected() + { + // Arrange + var testFramework = TestFrameworkFactory.GetFramework("xunit2"); + + // Act + Action act = () => testFramework.Throw("MyMessage"); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void When_running_test_with_unknown_test_framework_it_should_throw() + { + // Act + Action act = () => TestFrameworkFactory.GetFramework("foo"); + + // Assert + act.Should().Throw() + .WithMessage("*the test framework 'foo' but this is not supported*"); + } + + [Fact] + public void When_running_test_with_late_bound_but_unavailable_test_framework_it_should_throw() + { + // Act + Action act = () => TestFrameworkFactory.GetFramework("nunit"); + + act.Should().Throw() + .WithMessage("*test framework 'nunit' but the required assembly 'nunit.framework' could not be found*"); + } +} diff --git a/Tests/FluentAssertions.Specs/ConfigurationSpecs.cs b/Tests/FluentAssertions.Specs/ConfigurationSpecs.cs deleted file mode 100644 index 36cbc680ef..0000000000 --- a/Tests/FluentAssertions.Specs/ConfigurationSpecs.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Threading.Tasks; -using FluentAssertions.Common; -using Xunit; - -namespace FluentAssertions.Specs; - -[Collection("ConfigurationSpecs")] -public class ConfigurationSpecs -{ - [Fact] - public void When_concurrently_accessing_current_Configuration_no_exception_should_be_thrown() - { - // Act - Action act = () => Parallel.For( - 0, - 10000, - new ParallelOptions - { - MaxDegreeOfParallelism = 8 - }, - __ => - { - Configuration.Current.ValueFormatterAssembly = string.Empty; - _ = Configuration.Current.ValueFormatterDetectionMode; - } - ); - - // Assert - act.Should().NotThrow(); - } -} - -// Due to tests that call Configuration.Current -[CollectionDefinition("ConfigurationSpecs", DisableParallelization = true)] -public class ConfigurationSpecsDefinition; diff --git a/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs index 32d48f1360..a3279cd60a 100644 --- a/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs +++ b/Tests/FluentAssertions.Specs/Execution/AssertionScopeSpecs.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using FluentAssertions; -using FluentAssertions.Common; using FluentAssertions.Execution; using Xunit; using Xunit.Sdk; @@ -340,7 +339,7 @@ public void ThrowIfAny(IDictionary context) } } - Services.ThrowException(builder.ToString()); + AssertionEngine.TestFramework.Throw(builder.ToString()); } } @@ -355,7 +354,7 @@ internal class FailWithStupidMessageAssertionStrategy : IAssertionStrategy public IEnumerable FailureMessages => []; public void HandleFailure(string message) => - Services.ThrowException("Good luck with understanding what's going on!"); + AssertionEngine.TestFramework.Throw("Good luck with understanding what's going on!"); public IEnumerable DiscardFailures() => []; diff --git a/Tests/FluentAssertions.Specs/Execution/FallbackTestFrameworkTests.cs b/Tests/FluentAssertions.Specs/Execution/FallbackTestFrameworkTests.cs deleted file mode 100644 index f8231e0898..0000000000 --- a/Tests/FluentAssertions.Specs/Execution/FallbackTestFrameworkTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -using FluentAssertions.Execution; -using Xunit; - -namespace FluentAssertions.Specs.Execution; - -public class FallbackTestFrameworkTests -{ - [Fact] - public void The_fallback_test_framework_is_available() - { - var sut = new FallbackTestFramework(); - - sut.IsAvailable.Should().BeTrue(); - } - - [Fact] - public void Throwing_with_messages_throws_the_exception() - { - var sut = new FallbackTestFramework(); - - sut.Invoking(x => x.Throw("test message")).Should().ThrowExactly() - .WithMessage("test message"); - } -} diff --git a/Tests/FluentAssertions.Specs/Execution/TestFrameworkProviderTests.cs b/Tests/FluentAssertions.Specs/Execution/TestFrameworkProviderTests.cs deleted file mode 100644 index bf934d4c12..0000000000 --- a/Tests/FluentAssertions.Specs/Execution/TestFrameworkProviderTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using FluentAssertions.Common; -using FluentAssertions.Execution; -using Xunit; -using Xunit.Sdk; - -namespace FluentAssertions.Specs.Execution; - -public class TestFrameworkProviderTests -{ - [Fact] - public void When_running_xunit_test_implicitly_it_should_be_detected() - { - // Arrange - var configuration = new Configuration(new TestConfigurationStore()); - var testFrameworkProvider = new TestFrameworkProvider(configuration); - - // Act - Action act = () => testFrameworkProvider.Throw("MyMessage"); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_running_xunit_test_explicitly_it_should_be_detected() - { - // Arrange - var configuration = new Configuration(new TestConfigurationStore()) - { - TestFrameworkName = "xunit2" - }; - - var testFrameworkProvider = new TestFrameworkProvider(configuration); - - // Act - Action act = () => testFrameworkProvider.Throw("MyMessage"); - - // Assert - act.Should().Throw(); - } - - [Fact] - public void When_running_test_with_unknown_test_framework_it_should_throw() - { - // Arrange - var configuration = new Configuration(new TestConfigurationStore()) - { - TestFrameworkName = "foo" - }; - - var testFrameworkProvider = new TestFrameworkProvider(configuration); - - // Act - Action act = () => testFrameworkProvider.Throw("MyMessage"); - - // Assert - act.Should().Throw() - .WithMessage("*the test framework 'foo' but this is not supported*"); - } - - [Fact] - public void When_running_test_with_late_bound_but_unavailable_test_framework_it_should_throw() - { - // Arrange - var configuration = new Configuration(new TestConfigurationStore()) - { - TestFrameworkName = "nunit" - }; - - var testFrameworkProvider = new TestFrameworkProvider(configuration); - - // Act - Action act = () => testFrameworkProvider.Throw("MyMessage"); - - act.Should().Throw() - .WithMessage("*test framework 'nunit' but the required assembly 'nunit.framework' could not be found*"); - } - - private sealed class TestConfigurationStore : IConfigurationStore - { - string IConfigurationStore.GetSetting(string name) => string.Empty; - } -} diff --git a/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs b/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs index 56c5a2aeec..e8bb2f8224 100644 --- a/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs +++ b/Tests/FluentAssertions.Specs/Formatting/FormatterSpecs.cs @@ -14,7 +14,7 @@ namespace FluentAssertions.Specs.Formatting; [Collection("FormatterSpecs")] -public class FormatterSpecs +public sealed class FormatterSpecs : IDisposable { [Fact] public void When_value_contains_cyclic_reference_it_should_create_descriptive_error_message() @@ -210,44 +210,44 @@ public void When_the_object_is_a_generic_type_without_custom_string_representati // Assert act.Should().Throw() .WithMessage( - """ - Expected stuff to be equal to - { - FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] - { - Children = {1, 2, 3, 4}, - Description = "Stuff_1", - StuffId = 1 - }, - FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] - { - Children = {1, 2, 3, 4}, - Description = "WRONG_DESCRIPTION", - StuffId = 2 - } - }, but - { - FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] + """ + Expected stuff to be equal to { - Children = {1, 2, 3, 4}, - Description = "Stuff_1", - StuffId = 1 - }, - FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] + FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] + { + Children = {1, 2, 3, 4}, + Description = "Stuff_1", + StuffId = 1 + }, + FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] + { + Children = {1, 2, 3, 4}, + Description = "WRONG_DESCRIPTION", + StuffId = 2 + } + }, but { - Children = {1, 2, 3, 4}, - Description = "Stuff_2", - StuffId = 2 - } - } differs at index 1. - """); + FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] + { + Children = {1, 2, 3, 4}, + Description = "Stuff_1", + StuffId = 1 + }, + FluentAssertions.Specs.Formatting.FormatterSpecs+Stuff`1[[System.Int32*]] + { + Children = {1, 2, 3, 4}, + Description = "Stuff_2", + StuffId = 2 + } + } differs at index 1. + """); } [Fact] public void When_the_object_is_a_user_defined_type_it_should_show_the_name_on_the_initial_line() { // Arrange - var stuff = new StuffRecord(42, "description", new(24), [10, 20, 30, 40]); + var stuff = new StuffRecord(42, "description", new ChildRecord(24), [10, 20, 30, 40]); // Act Action act = () => stuff.Should().BeNull(); @@ -255,18 +255,18 @@ public void When_the_object_is_a_user_defined_type_it_should_show_the_name_on_th // Assert act.Should().Throw() .Which.Message.Should().Match( - """ - Expected stuff to be , but found FluentAssertions.Specs.Formatting.FormatterSpecs+StuffRecord - { - RecordChildren = {10, 20, 30, 40}, - RecordDescription = "description", - RecordId = 42, - SingleChild = FluentAssertions.Specs.Formatting.FormatterSpecs+ChildRecord + """ + Expected stuff to be , but found FluentAssertions.Specs.Formatting.FormatterSpecs+StuffRecord { - ChildRecordId = 24 - } - }. - """); + RecordChildren = {10, 20, 30, 40}, + RecordDescription = "description", + RecordId = 42, + SingleChild = FluentAssertions.Specs.Formatting.FormatterSpecs+ChildRecord + { + ChildRecordId = 24 + } + }. + """); } [Fact] @@ -292,28 +292,29 @@ public void When_the_object_is_an_anonymous_type_it_should_show_the_properties_r // Assert act.Should().Throw() .Which.Message.Should().Be( - """ - Expected stuff to be - { - Children = {10, 20, 30, 40}, - SingleChild = + """ + Expected stuff to be { - ChildId = 4 - } - }, but found - { - Children = {1, 2, 3, 4}, - Description = "absent", - SingleChild = + Children = {10, 20, 30, 40}, + SingleChild = + { + ChildId = 4 + } + }, but found { - ChildId = 8 - } - }. - """); + Children = {1, 2, 3, 4}, + Description = "absent", + SingleChild = + { + ChildId = 8 + } + }. + """); } [Fact] - public void When_the_object_is_a_list_of_anonymous_type_it_should_show_the_properties_recursively_with_newlines_and_indentation() + public void + When_the_object_is_a_list_of_anonymous_type_it_should_show_the_properties_recursively_with_newlines_and_indentation() { // Arrange var stuff = new[] @@ -346,32 +347,32 @@ public void When_the_object_is_a_list_of_anonymous_type_it_should_show_the_prope // Assert act.Should().Throw() .Which.Message.Should().Match( - """ - Expected stuff to be a collection with 1 item(s), but* - { + """ + Expected stuff to be a collection with 1 item(s), but* { - Description = "absent" - },* - { - Description = "absent" + { + Description = "absent" + },* + { + Description = "absent" + } } - } - contains 1 item(s) more than + contains 1 item(s) more than - { { - ComplexChildren =* { + ComplexChildren =* { - Property = "hello" - },* - { - Property = "goodbye" + { + Property = "hello" + },* + { + Property = "goodbye" + } } } - } - }.* - """); + }.* + """); } [Fact] @@ -404,19 +405,19 @@ public void When_the_object_is_a_tuple_it_should_show_the_properties_recursively // Assert act.Should().Throw() .Which.Message.Should().Match( - """ - Expected stuff to be equal to* - { - Item1 = 2,* - Item2 = "WRONG_DESCRIPTION",* - Item3 = {4, 5, 6, 7} - }, but found* - { - Item1 = 1,* - Item2 = "description",* - Item3 = {1, 2, 3, 4} - }.* - """); + """ + Expected stuff to be equal to* + { + Item1 = 2,* + Item2 = "WRONG_DESCRIPTION",* + Item3 = {4, 5, 6, 7} + }, but found* + { + Item1 = 1,* + Item2 = "description",* + Item3 = {1, 2, 3, 4} + }.* + """); } [Fact] @@ -426,7 +427,7 @@ public void When_the_object_is_a_record_it_should_show_the_properties_recursivel var stuff = new StuffRecord( RecordId: 9, RecordDescription: "descriptive", - SingleChild: new(ChildRecordId: 80), + SingleChild: new ChildRecord(ChildRecordId: 80), RecordChildren: [4, 5, 6, 7]); var expectedStuff = new @@ -440,21 +441,21 @@ public void When_the_object_is_a_record_it_should_show_the_properties_recursivel // Assert act.Should().Throw() .Which.Message.Should().Match( - """ - Expected stuff to be* - { - RecordDescription = "WRONG_DESCRIPTION" - }, but found FluentAssertions.Specs.Formatting.FormatterSpecs+StuffRecord - { - RecordChildren = {4, 5, 6, 7},* - RecordDescription = "descriptive",* - RecordId = 9,* - SingleChild = FluentAssertions.Specs.Formatting.FormatterSpecs+ChildRecord + """ + Expected stuff to be* { - ChildRecordId = 80 - } - }. - """); + RecordDescription = "WRONG_DESCRIPTION" + }, but found FluentAssertions.Specs.Formatting.FormatterSpecs+StuffRecord + { + RecordChildren = {4, 5, 6, 7},* + RecordDescription = "descriptive",* + RecordId = 9,* + SingleChild = FluentAssertions.Specs.Formatting.FormatterSpecs+ChildRecord + { + ChildRecordId = 80 + } + }. + """); } [Fact] @@ -950,7 +951,7 @@ private record ChildRecord(int ChildRecordId); public void When_a_custom_formatter_exists_in_any_loaded_assembly_it_should_override_the_default_formatters() { // Arrange - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; var subject = new SomeClassWithCustomFormatter { @@ -968,7 +969,7 @@ public void When_a_custom_formatter_exists_in_any_loaded_assembly_it_should_over public void When_a_base_class_has_a_custom_formatter_it_should_override_the_default_formatters() { // Arrange - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; var subject = new SomeClassInheritedFromClassWithCustomFormatterLvl1 { @@ -986,7 +987,7 @@ public void When_a_base_class_has_a_custom_formatter_it_should_override_the_defa public void When_there_are_multiple_custom_formatters_it_should_select_a_more_specific_one() { // Arrange - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; var subject = new SomeClassInheritedFromClassWithCustomFormatterLvl2 { @@ -1004,7 +1005,7 @@ public void When_there_are_multiple_custom_formatters_it_should_select_a_more_sp public void When_a_base_class_has_multiple_custom_formatters_it_should_work_the_same_as_for_the_base_class() { // Arrange - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Scan; var subject = new SomeClassInheritedFromClassWithCustomFormatterLvl3 { @@ -1022,7 +1023,7 @@ public void When_a_base_class_has_multiple_custom_formatters_it_should_work_the_ public void When_no_custom_formatter_exists_in_the_specified_assembly_it_should_use_the_default() { // Arrange - Configuration.Current.ValueFormatterAssembly = "FluentAssertions"; + AssertionConfiguration.Current.Formatting.ValueFormatterAssembly = "FluentAssertions"; var subject = new SomeClassWithCustomFormatter { @@ -1040,7 +1041,7 @@ public void When_no_custom_formatter_exists_in_the_specified_assembly_it_should_ public void When_formatter_scanning_is_disabled_it_should_use_the_default_formatters() { // Arrange - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Disabled; + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Disabled; var subject = new SomeClassWithCustomFormatter { @@ -1058,8 +1059,7 @@ public void When_formatter_scanning_is_disabled_it_should_use_the_default_format public void When_no_formatter_scanning_is_configured_it_should_use_the_default_formatters() { // Arrange - Services.ResetToDefaults(); - Configuration.Current.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Disabled; + AssertionConfiguration.Current.Formatting.ValueFormatterDetectionMode = ValueFormatterDetectionMode.Disabled; var subject = new SomeClassWithCustomFormatter { @@ -1217,6 +1217,8 @@ public FormatterScope(IValueFormatter formatter) public void Dispose() => Formatter.RemoveFormatter(formatter); } + + public void Dispose() => AssertionEngine.ResetToDefaults(); } // Due to the tests that call Configuration.Current