From 8b06e8c988270cc525d6ba6b3c2c81c8cd1f060e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Guldmund?= Date: Sat, 13 Jan 2024 20:13:43 +0100 Subject: [PATCH] #4 Apply test hierarchy in ShimTests.cs --- test/Pose.Tests/ShimTests.cs | 1359 ++++++++++++++++++++-------------- 1 file changed, 812 insertions(+), 547 deletions(-) diff --git a/test/Pose.Tests/ShimTests.cs b/test/Pose.Tests/ShimTests.cs index 86ea440..8780d3f 100644 --- a/test/Pose.Tests/ShimTests.cs +++ b/test/Pose.Tests/ShimTests.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using System.Reflection; using System.Threading; using FluentAssertions; using Pose.Exceptions; @@ -16,617 +15,883 @@ namespace Pose.Tests { public class ShimTests { - [Fact] - public void Can_replace_static_method() + public class Methods { - // Act - var shim = Shim.Replace(() => Console.WriteLine("")); - - // Assert - var consoleWriteLineMethodInfo = typeof(Console).GetMethod(nameof(Console.WriteLine), new[] { typeof(string) }); - consoleWriteLineMethodInfo.Should().BeSameAs(shim.Original.As(), because: "the shim represents that method"); - shim.Replacement.Should().BeNull(because: "no replacement has been specified"); - } - - [Fact] - public void Can_replace_instance_method_of_specific_instance() - { - // Arrange - var instance = new Instance(); + public class StaticTypes + { + private class Instance + { + public static string StaticMethod() => null; + } - // Act - var shim = Shim.Replace(() => instance.GetString()); + [Fact] + public void Can_shim_static_method() + { + // Arrange + const string shimmedValue = "String"; + + var shim = Shim + .Replace(() => Instance.StaticMethod()) + .With(() => "String"); + + // Act + string returnedValue = default; + PoseContext.Isolate( + () => { returnedValue = Instance.StaticMethod(); }, + shim + ); - // Assert - var methodInfo = typeof(Instance).GetMethod(nameof(Instance.GetString)); + // Assert + returnedValue.Should().BeEquivalentTo(shimmedValue, because: "that is what the shim is configured to return"); + } + } - shim.Original.Should().Be(methodInfo, because: "the shim represents that method"); - shim.Instance.Should().BeSameAs(instance, because: "the shim is configured for this specific instance"); - shim.Replacement.Should().BeNull(because: "no replacement has been specified"); - shim.Instance.Should().NotBeSameAs(new Instance(), because: "the shim is configured for a specific instance"); - } + public class ReferenceTypes + { + private class Instance + { + // ReSharper disable once MemberCanBeMadeStatic.Local + public string GetString() + { + return "!"; + } + } + + [Fact] + public void Can_shim_method_of_any_instance() + { + // Arrange + var action = new Func((Instance @this) => "String"); + var shim = Shim.Replace(() => Is.A().GetString()).With(action); + + // Act + string dt = default; + PoseContext.Isolate( + () => + { + var instance = new Instance(); + dt = instance.GetString(); + }, shim); + + // Assert + dt.Should().BeEquivalentTo("String", because: "that is what the shim is configured to return"); + } - [Fact] - public void Throws_InvalidShimSignatureException_if_the_signature_of_the_replacement_does_not_match() - { - // Arrange - var shimTests = new Instance(); + [Fact] + public void Can_shim_method_of_specific_instance() + { + // Arrange + const string configuredValue = "String"; + + var instance = new Instance(); + var shim = Shim + .Replace(() => instance.GetString()) + .With((Instance _) => configuredValue); + + // Act + string value = default; + PoseContext.Isolate( + () => { value = instance.GetString(); }, + shim + ); - // Act - Action act = () => Shim.Replace(() => shimTests.GetString()).With(() => { }); // Targets Shim.Replace(Expression>) - Action act1 = () => Shim.Replace(() => Console.WriteLine(Is.A())).With(() => { }); // Targets Shim.Replace(Expression) + // Assert + value.Should().BeEquivalentTo(configuredValue, because: "that is what the shim is configured to return"); + } + + [Fact] + public void Shims_only_the_method_of_the_specified_instance() + { + // Arrange + var shimmedInstance = new Instance(); + var shim = Shim + .Replace(() => shimmedInstance.GetString()) + .With(new Func((Instance @this) => "String")); + + // Act + string responseFromShimmedInstance = default; + string responseFromNonShimmedInstance = default; + PoseContext.Isolate( + () => + { + responseFromShimmedInstance = shimmedInstance.GetString(); + var nonShimmedInstance = new Instance(); + responseFromNonShimmedInstance = nonShimmedInstance.GetString(); + }, shim); - // Assert - act.Should().Throw(because: "the signature of the replacement method does not match the original"); - act1.Should().Throw(because: "the signature of the replacement method does not match the original"); - } - - [Fact] - public void Reports_types_when_throwing_InvalidShimSignatureException() - { - // Arrange - var shimTests = new Instance(); + // Assert + responseFromShimmedInstance.Should().BeEquivalentTo("String", because: "that is what the shim is configured to return"); + responseFromNonShimmedInstance.Should().NotBeEquivalentTo("String", because: "the shim is configured for a specific instance"); + responseFromNonShimmedInstance.Should().BeEquivalentTo("!", because: "that is what the instance returns by default"); + } + } - // Act - Action act = () => Shim.Replace(() => shimTests.GetString()).With(() => { }); // Targets Shim.Replace(Expression>) - Action act1 = () => Shim.Replace(() => Console.WriteLine(Is.A())).With(() => { }); // Targets Shim.Replace(Expression) - Action act2 = () => Shim.Replace(() => Is.A().Date).With((DateTime @this) => { return new DateTime(2004, 1, 1); }); // Targets Shim.Replace(Expression) - Action act3 = () => Shim.Replace(() => Is.A().Date).With((ref TimeSpan @this) => { return new DateTime(2004, 1, 1); }); // Targets Shim.Replace(Expression) + public class ValueTypes + { + private struct InstanceValue + { + public string GetString() => null; + } + + [Fact] + public void Can_shim_instance_method_of_value_type() + { + // Arrange + const string configuredValue = "String"; + var shim = Shim + .Replace(() => Is.A().GetString()) + .With((ref InstanceValue @this) => configuredValue); + + // Act + string value = default; + PoseContext.Isolate( + () => { value = new InstanceValue().GetString(); }, + shim + ); - // Assert - act.Should() - .Throw(because: "the signature of the replacement method does not match the original") - .WithMessage("*Expected System.String* Got System.Void"); - act1.Should() - .Throw(because: "the signature of the replacement method does not match the original") - .WithMessage("*Expected 1. Got 0*"); - act2.Should() - .Throw(because: "value types must be passed by ref") - .WithMessage("*ValueType instances must be passed by ref*"); - act3.Should() - .Throw(because: "the signature of the replacement method does not match the original") - .WithMessage("*Expected System.DateTime* Got System.TimeSpan*"); - } + // Assert + value.Should().BeEquivalentTo(configuredValue, because: "that is what the shim is configured to return"); + } - [Fact] - public void TestShimReplaceWith() - { - // Arrange - var shimTests = new Instance(); - var action = new Action(() => { }); - var actionInstance = new Action(s => { }); - - // Act - var shim = Shim.Replace(() => Console.WriteLine()).With(action); - var shim1 = Shim.Replace(() => shimTests.VoidMethod()).With(actionInstance); - - // Assert - var consoleWriteLineMethod = typeof(Console).GetMethod(nameof(Console.WriteLine), Type.EmptyTypes); - shim.Original.Should().BeSameAs(consoleWriteLineMethod); - shim.Replacement.Should().BeSameAs(action); - - var voidMethod = typeof(Instance).GetMethod(nameof(Instance.VoidMethod)); - shim1.Original.Should().BeSameAs(voidMethod, because: "the shim is configured for this method"); - shim1.Instance.Should().BeSameAs(shimTests, because: "the shim is configured for this instance"); - shim1.Replacement.Should().BeSameAs(actionInstance, because: "that is the shim's replacement"); - } + } + + public class AbstractMethods + { + private abstract class AbstractBase + { + public virtual string GetStringFromAbstractBase() => "!"; - [Fact] - public void TestReplacePropertyGetter() - { - var shim = Shim.Replace(() => Thread.CurrentThread.CurrentCulture); + public abstract string GetAbstractString(); + } - var currentCultureGetMethod = typeof(Thread).GetProperty(nameof(Thread.CurrentCulture), typeof(CultureInfo)).GetMethod; - shim.Original.Should().BeEquivalentTo(currentCultureGetMethod, because: "the shim configures that method"); - shim.Replacement.Should().BeNull(because: "no replacement is configured"); - } + private class DerivedFromAbstractBase : AbstractBase + { + public override string GetAbstractString() => throw new NotImplementedException(); + } - [Fact] - public void TestReplacePropertySetter() - { - // Arrange - var shim = Shim.Replace(() => Is.A().CurrentCulture, true); + private class ShadowsMethodFromAbstractBase : AbstractBase + { + public override string GetStringFromAbstractBase() => "Shadow"; + + public override string GetAbstractString() => throw new NotImplementedException(); + } + + [Fact] + public void Can_shim_instance_method_of_abstract_type() + { + // Arrange + var action = new Func((AbstractBase @this) => { return "Hello"; }); + var shim = Shim + .Replace(() => Is.A().GetStringFromAbstractBase()) + .With(action); + + // Act + string dt = default; + PoseContext.Isolate( + () => + { + var instance = new DerivedFromAbstractBase(); + dt = instance.GetStringFromAbstractBase(); + }, + shim + ); + + // Assert + dt.Should().BeEquivalentTo("Hello", because: "the shim configured the base class"); + } + + [Fact] + public void Can_shim_abstract_method_of_abstract_type() + { + // Arrange + const string returnValue = "Hello"; + + var wasCalled = false; + var action = new Func( + (AbstractBase @this) => + { + wasCalled = true; + return returnValue; + }); + var shim = Shim + .Replace(() => Is.A().GetAbstractString()) + .With(action); + + // Act + string dt = default; + wasCalled.Should().BeFalse(because: "no calls have been made yet"); + // ReSharper disable once SuggestVarOrType_SimpleTypes + Action act = () => PoseContext.Isolate( + () => + { + var instance = new DerivedFromAbstractBase(); + dt = instance.GetAbstractString(); + }, + shim + ); + + // Assert + act.Should().NotThrow(because: "the shim works"); + wasCalled.Should().BeTrue(because: "the shim has been invoked"); + dt.Should().BeEquivalentTo(returnValue, because: "the shim configured the base class"); + } + + [Fact] + public void Shim_is_not_invoked_if_method_is_overriden_in_derived_type() + { + // Arrange + var wasCalled = false; + var action = new Func( + (AbstractBase @this) => + { + wasCalled = true; + return "Hello"; + }); + var shim = Shim + .Replace(() => Is.A().GetStringFromAbstractBase()) + .With(action); + + // Act + string dt = default; + wasCalled.Should().BeFalse(because: "no calls have been made yet"); + PoseContext.Isolate( + () => + { + var instance = new ShadowsMethodFromAbstractBase(); + dt = instance.GetStringFromAbstractBase(); + }, + shim + ); + + // Assert + var _ = new ShadowsMethodFromAbstractBase(); + dt.Should().BeEquivalentTo(_.GetStringFromAbstractBase(), because: "the shim configured the base class"); + wasCalled.Should().BeFalse(because: "the shim was not invoked"); + } + } - var currentCultureSetMethod = typeof(Thread).GetProperty(nameof(Thread.CurrentCulture), typeof(CultureInfo)).SetMethod; - shim.Original.Should().BeEquivalentTo(currentCultureSetMethod, because: "the shim configures that method"); - shim.Replacement.Should().BeNull(because: "no replacement is configured"); - } - - - [Fact] - public void TestReplacePropertySetterAction() - { - // Arrange - var getterExecuted = false; - var getterShim = Shim - .Replace(() => Is.A().CurrentCulture) - .With((Thread t) => - { - getterExecuted = true; - return t.CurrentCulture; - }); - - var setterExecuted = false; - var setterShim = Shim - .Replace(() => Is.A().CurrentCulture, true) - .With((Thread t, CultureInfo value) => - { - setterExecuted = true; - t.CurrentCulture = value; - }); - - // Pre-Act asserts - var currentCultureProperty = typeof(Thread).GetProperty(nameof(Thread.CurrentCulture), typeof(CultureInfo)); - getterShim.Original.Should().BeEquivalentTo(currentCultureProperty.GetMethod, because: "the shim configures that method"); - setterShim.Original.Should().BeEquivalentTo(currentCultureProperty.SetMethod, because: "the shim configures that method"); - - // Act - PoseContext.Isolate(() => + public class SealedTypes { - var oldCulture = Thread.CurrentThread.CurrentCulture; - Thread.CurrentThread.CurrentCulture = oldCulture; - }, getterShim, setterShim); + private sealed class SealedClass + { + public string GetSealedString() => nameof(GetSealedString); + } + + [Fact] + public void Can_shim_method_of_sealed_class() + { + // Arrange + var action = new Func((SealedClass @this) => "String"); + var shim = Shim.Replace(() => Is.A().GetSealedString()).With(action); + + // Act + string dt = default; + PoseContext.Isolate( + () => + { + var instance = new SealedClass(); + dt = instance.GetSealedString(); + }, + shim + ); + + // Assert + dt.Should().BeEquivalentTo("String", because: "that is what the shim is configured to return"); - // Assert - getterExecuted.Should().BeTrue(because: "the shim was executed"); - setterExecuted.Should().BeTrue(because: "the shim was executed"); + var sealedClass = new SealedClass(); + dt.Should().NotBeEquivalentTo(sealedClass.GetSealedString(), because: "that is the original value"); + } + } } - [Fact] - public void Can_shim_static_property() + public class Getters { - // Arrange - var action = new Func(() => new DateTime(2004, 1, 1)); - var shim = Shim - .Replace(() => DateTime.Now) - .With(action); - - // Act - DateTime dt = default; - PoseContext.Isolate( - () => { dt = DateTime.Now; }, - shim - ); - - // Assert - dt.Should().BeCloseTo(new DateTime(2004, 1, 1), TimeSpan.Zero, because: "that is what the shim returns"); - } + public class StaticTypes + { + private class Instance + { + public static string StaticString { get; set; } + } - private class Instance - { - public string Text { get; set; } = "_"; + [Fact] + public void Can_shim_static_property_getter() + { + // Arrange + var shim = Shim + .Replace(() => Instance.StaticString) + .With(() => "Hello"); + + // Act + string value = default; + PoseContext.Isolate(() => { value = Instance.StaticString; }, shim); - public string GetString() - { - return "!"; + // Assert + value.Should().BeEquivalentTo("Hello", because: "that is what the shim is configured to return"); + } } - public static string StaticString { get; set; } = "?"; - - public static string StaticMethod() + public class ReferenceTypes { - return "0"; - } + private class Instance + { + public string Text { get; set; } + } - public void VoidMethod() { } - } - - private struct InstanceValue - { - public string GetString() - { - return "!"; + [Fact] + public void Can_shim_property_getter_of_specific_instance() + { + // Arrange + var instance = new Instance(); + var action = new Func((Instance @this) => "Hello"); + var shim = Shim.Replace(() => instance.Text).With(action); + + // Act + string dt = default; + Instance nonShimmedInstance = default; + string textFromNonShimmedInstance = default; + PoseContext.Isolate( + () => + { + dt = instance.Text; + + nonShimmedInstance = new Instance(); + textFromNonShimmedInstance = nonShimmedInstance.Text; + }, shim); + + // Assert + dt.Should().BeEquivalentTo("Hello", because: "that is what the shim is configured to return"); + + textFromNonShimmedInstance.Should().NotBeEquivalentTo(dt, because: "the shim is for a specific instance"); + } + + [Fact] + public void Can_shim_property_getter_of_any_instance() + { + // Arrange + var action = new Func((Instance @this) => "Hello"); + var shim = Shim.Replace(() => Is.A().Text).With(action); + + // Act + string value1 = default; + string value2 = default; + PoseContext.Isolate( + () => + { + value1 = new Instance().Text; + value2 = new Instance().Text; + }, shim); + + // Assert + value1.Should().BeEquivalentTo(value2, because: "the shim is configured for any instance"); + + var instance = new Instance(); + instance.Text.Should().NotBeEquivalentTo(value1, because: "this instance is created outside the isolated code"); + } } - } - - [Fact] - public void Can_shim_instance_property_getter() - { - // Arrange - var instance = new Instance(); - var action = new Func((Instance @this) => "Hello"); - var shim = Shim.Replace(() => instance.Text).With(action); - - // Act - string dt = default; - PoseContext.Isolate(() => { dt = instance.Text; }, shim); - // Assert - dt.Should().BeEquivalentTo("Hello", because: "that is what the shim is configured to return"); - } - - [Fact] - public void Can_shim_instance_property_setter() - { - // Arrange - var instance = new Instance(); - var wasCalled = false; - var action = new Action((Instance @this, string prop) => { wasCalled = true; }); + public class ValueTypes + { + private struct InstanceValue + { + public string Text { get; set; } + } + + [Fact] + public void Cannot_shim_property_getter_of_specific_instance() + { + // Arrange + var instance = new InstanceValue(); + Action act = () => Shim + .Replace(() => instance.Text) + .With((ref InstanceValue @this) => string.Empty); + + // Assert + act.Should().Throw(because: "instance methods on specific value type instances cannot be replaced"); + } + + [Fact] + public void Can_shim_property_getter_of_any_instance() + { + // Arrange + var shim = Shim + .Replace(() => Is.A().Text) + .With((ref InstanceValue @this) => "Hello"); + + // Act + string value1 = default; + string value2 = default; + PoseContext.Isolate( + () => + { + value1 = new InstanceValue().Text; + value2 = new InstanceValue().Text; + }, shim); - // Act - var shim = Shim.Replace(() => Is.A().Text, true).With(action); + // Assert + value1.Should().BeEquivalentTo(value2, because: "the shim is configured for any instance"); - // Assert - wasCalled.Should().BeFalse(because: "the shim has not been called yet"); - PoseContext.Isolate(() => { instance.Text = "Hello"; }, shim); - wasCalled.Should().BeTrue(because: "the shim has been called"); - } - - [Fact] - public void Can_shim_static_property_getter() - { - // Arrange - var action = new Func(() => "Hello"); - var shim = Shim.Replace(() => Instance.StaticString).With(action); + var instance = new InstanceValue(); + instance.Text.Should().NotBeEquivalentTo(value1, because: "this instance is created outside the isolated code"); + } + } - // Act - string dt = default; - PoseContext.Isolate(() => { dt = Instance.StaticString; }, shim); + public class SealedTypes + { + private sealed class SealedClass + { + public string SealedString { get; set; } = nameof(SealedString); + } + + [Fact] + public void Can_shim_property_getter_of_sealed_class() + { + // Arrange + var action = new Func((SealedClass @this) => "String"); + var shim = Shim + .Replace(() => Is.A().SealedString) + .With(action); + + // Act + string dt = default; + PoseContext.Isolate( + () => + { + var instance = new SealedClass(); + dt = instance.SealedString; + }, + shim + ); - // Assert - dt.Should().BeEquivalentTo("Hello", because: "that is what the shim is configured to return"); - } - - [Fact] - public void Can_shim_static_property_setter() - { - // Arrange - var wasCalled = false; - var action = new Action(prop => { wasCalled = true; }); - var shim = Shim.Replace(() => Instance.StaticString, true).With(action); - - // Act + Assert - wasCalled.Should().BeFalse(because: "the shim has not been called yet"); - PoseContext.Isolate(() => { Instance.StaticString = "Hello"; }, shim); - wasCalled.Should().BeTrue(because: "the shim has been called"); + // Assert + dt.Should().BeEquivalentTo("String", because: "that is what the shim is configured to return"); + + var sealedClass = new SealedClass(); + dt.Should().NotBeEquivalentTo(sealedClass.SealedString, because: "that is the original value"); + } + } } - [Fact] - public void Can_shim_instance_method() + public class Setters { - // Arrange - var action = new Func((Instance @this) => "String"); - var shim = Shim.Replace(() => Is.A().GetString()).With(action); + public class StaticTypes + { + private class Instance + { + public static string StaticString { get; set; } + } + + [Fact] + public void Can_shim_static_property_setter() + { + // Arrange + var wasCalled = false; + var shim = Shim + .Replace(() => Instance.StaticString, true) + .With(new Action(_ => { wasCalled = true; })); + + // Pre-Act Assert + wasCalled.Should().BeFalse(because: "the shim has not been called yet"); + + // Act + PoseContext.Isolate(() => { Instance.StaticString = "Hello"; }, shim); + + // Assert + wasCalled.Should().BeTrue(because: "the shim has been called"); + } + } - // Act - string dt = default; - PoseContext.Isolate( - () => + public class ReferenceTypes + { + private class Instance + { + public string Text { get; set; } + } + + [Fact] + public void Can_shim_property_setter_of_any_instance() + { + // Arrange + var invocationCount = 0; + + var shim = Shim + .Replace(() => Is.A().Text, true) + .With((Instance @this, string prop) => { invocationCount++; }); + + // Pre-act assert + invocationCount.Should().Be(0, because: "the shim has not been called yet"); + + // Act + PoseContext.Isolate( + () => + { + new Instance().Text = "Hello"; + new Instance().Text = "Hello"; + }, shim); + + // Assert + invocationCount.Should().Be(2, because: "the shim was invoked 2 times"); + } + + [Fact] + public void Can_shim_property_setter_of_specific_instance() { + // Arrange var instance = new Instance(); - dt = instance.GetString(); - }, shim); - - // Assert - dt.Should().BeEquivalentTo("String", because: "that is what the shim is configured to return"); - } - - [Fact] - public void Can_shim_instance_method_of_value_type() - { - // Arrange - var shim = Shim - .Replace(() => Is.A().GetString()) - .With(delegate(ref InstanceValue @this) { return "String"; }); - - // Act - string dt = default; - PoseContext.Isolate( - () => { dt = new InstanceValue().GetString(); }, - shim - ); + var wasCalled = false; + var action = new Action((Instance @this, string prop) => { wasCalled = true; }); - // Assert - dt.Should().BeEquivalentTo("String", because: "that is what the shim is configured to return"); - } - - [Fact] - public void Can_shim_instance_method_of_specific_instance() - { - // Arrange - var instance = new Instance(); - var action = new Func((Instance @this) => "String"); - var shim = Shim.Replace(() => instance.GetString()).With(action); - - // Act - string dt = default; - PoseContext.Isolate( - () => { dt = instance.GetString(); }, - shim - ); + // Act + var shim = Shim.Replace(() => Is.A().Text, true).With(action); + + // Assert + wasCalled.Should().BeFalse(because: "the shim has not been called yet"); + PoseContext.Isolate(() => { instance.Text = "Hello"; }, shim); + wasCalled.Should().BeTrue(because: "the shim has been called"); + } + } - // Assert - dt.Should().BeEquivalentTo("String", because: "that is what the shim is configured to return"); - } + public class ValueTypes + { - [Fact] - public void Shims_only_the_method_of_the_specified_instance() - { - // Arrange - var shimmedInstance = new Instance(); - var action = new Func((Instance @this) => "String"); - var shim = Shim.Replace(() => shimmedInstance.GetString()).With(action); - - // Act - string responseFromShimmedInstance = default; - string responseFromNonShimmedInstance = default; - PoseContext.Isolate( - () => - { - responseFromShimmedInstance = shimmedInstance.GetString(); - var nonShimmedInstance = new Instance(); - responseFromNonShimmedInstance = nonShimmedInstance.GetString(); - }, shim); + } + + public class SealedTypes + { + private sealed class SealedClass + { + public string SealedString { get; set; } = nameof(SealedString); + } + + [Fact] + public void Can_shim_property_setter_of_sealed_class() + { + // Arrange + var wasCalled = false; + var action = new Action((SealedClass @this, string value) => + { + wasCalled = true; + @this.SealedString = "Something"; + } + ); + var shim = Shim + .Replace(() => Is.A().SealedString, setter: true) + .With(action); + + // Act + wasCalled.Should().BeFalse(because: "no calls have been made yet"); + string dt = default; + PoseContext.Isolate( + () => + { + var instance = new SealedClass(); + instance.SealedString = "!!!"; + dt = instance.SealedString; + }, + shim + ); - // Assert - responseFromShimmedInstance.Should().BeEquivalentTo("String", because: "that is what the shim is configured to return"); - responseFromNonShimmedInstance.Should().NotBeEquivalentTo("String", because: "the shim is configured for a specific instance"); - responseFromNonShimmedInstance.Should().BeEquivalentTo("!", because: "that is what the instance returns by default"); - } - - [Fact] - public void Can_shim_static_method() - { - // Arrange - var action = new Func(() => "String"); - var shim = Shim.Replace(() => Instance.StaticMethod()).With(action); - - // Act - string dt = default; - PoseContext.Isolate( - () => { dt = Instance.StaticMethod(); }, - shim - ); + // Assert + wasCalled.Should().BeTrue(because: "the shim has been invoked"); + dt.Should().BeEquivalentTo("Something", because: "that is what the shim is configured to return"); - // Assert - dt.Should().BeEquivalentTo("String", because: "that is what the shim is configured to return"); + var sealedClass = new SealedClass(); + dt.Should().NotBeEquivalentTo(sealedClass.SealedString, because: "that is the original value"); + } + } } - [Fact] - public void Can_shim_constructor() + public class Constructors { - // Arrange - var action = new Func(() => new Instance(){Text = nameof(Instance.Text)}); - var shim = Shim - .Replace(() => new Instance()) - .With(action); - - // Act - Instance dt = default; - PoseContext.Isolate( - () => { dt = new Instance(); }, - shim - ); - - // Assert - dt.Text.Should().BeEquivalentTo(nameof(Instance.Text), because: "that is what the shim is configured to return"); - } + public class General + { + private class Instance + { + public string Text { get; set; } + } - [Fact] - public void Can_invoke_constructor_in_isolation() - { - // Arrange - Instance x = null; - Action act = () => PoseContext.Isolate( - () => + [Fact] + public void Can_invoke_constructor_in_isolation() { - x = new Instance(); // Specifically this line should *not* fail! - x.Text = nameof(Instance.Text); + // Arrange + Instance x = null; + Action act = () => PoseContext.Isolate( + () => + { + x = new Instance(); // Specifically this line should *not* fail! + x.Text = nameof(Instance.Text); + } + ); + + // Act + Assert + act.Should().NotThrow(because: "the constructor can be invoked in isolation"); + x.Should().NotBeNull(because: "the instance has been initialized in isolation"); + x.Text.Should().BeEquivalentTo(nameof(Instance.Text), because: "the property was set in isolation"); + } - ); + } - // Act + Assert - act.Should().NotThrow(because: "the constructor can be invoked in isolation"); - x.Should().NotBeNull(because: "the instance has been initialized in isolation"); - x.Text.Should().BeEquivalentTo(nameof(Instance.Text), because: "the property was set in isolation"); + public class StaticTypes + { + private static class Instance + { + public static string Text { get; set; } - } + static Instance() + { + + } + } - private abstract class AbstractBase - { - public virtual string GetStringFromAbstractBase() => "!"; + [Fact(Skip = "Not currently possible")] + public void Can_shim_constructor_of_sealed_reference_type() + { + // // Arrange + // var action = new Func(() => new Instance(){Text = nameof(Instance.Text)}); + // var shim = Shim + // .Replace(() => new Instance()) + // .With(action); + // + // // Act + // Instance dt = default; + // PoseContext.Isolate( + // () => { dt = new Instance(); }, + // shim + // ); + // + // // Assert + // dt.Text.Should().BeEquivalentTo(nameof(Instance.Text), because: "that is what the shim is configured to return"); + } - public abstract string GetAbstractString(); - } + } - private class DerivedFromAbstractBase : AbstractBase - { - public override string GetAbstractString() => throw new NotImplementedException(); - } + public class ReferenceTypes + { + private class Instance + { + public string Text { get; set; } + } - private class ShadowsMethodFromAbstractBase : AbstractBase - { - public override string GetStringFromAbstractBase() => "Shadow"; - - public override string GetAbstractString() => throw new NotImplementedException(); - } - - [Fact] - public void Can_shim_instance_method_of_abstract_type() - { - // Arrange - var action = new Func((AbstractBase @this) => { return "Hello"; }); - var shim = Shim - .Replace(() => Is.A().GetStringFromAbstractBase()) - .With(action); - - // Act - string dt = default; - PoseContext.Isolate( - () => - { - var instance = new DerivedFromAbstractBase(); - dt = instance.GetStringFromAbstractBase(); - }, - shim - ); - - // Assert - dt.Should().BeEquivalentTo("Hello", because: "the shim configured the base class"); - } - - [Fact] - public void Can_shim_abstract_method_of_abstract_type() - { - // Arrange - const string returnValue = "Hello"; - - var wasCalled = false; - var action = new Func( - (AbstractBase @this) => - { - wasCalled = true; - return returnValue; - }); - var shim = Shim - .Replace(() => Is.A().GetAbstractString()) - .With(action); - - // Act - string dt = default; - wasCalled.Should().BeFalse(because: "no calls have been made yet"); - // ReSharper disable once SuggestVarOrType_SimpleTypes - Action act = () => PoseContext.Isolate( - () => - { - var instance = new DerivedFromAbstractBase(); - dt = instance.GetAbstractString(); - }, - shim - ); + [Fact] + public void Can_shim_constructor_of_reference_type() + { + // Arrange + var action = new Func(() => new Instance(){Text = nameof(Instance.Text)}); + var shim = Shim + .Replace(() => new Instance()) + .With(action); + + // Act + Instance dt = default; + PoseContext.Isolate( + () => { dt = new Instance(); }, + shim + ); - // Assert - act.Should().NotThrow(because: "the shim works"); - wasCalled.Should().BeTrue(because: "the shim has been invoked"); - dt.Should().BeEquivalentTo(returnValue, because: "the shim configured the base class"); - } - - [Fact] - public void Shim_is_not_invoked_if_method_is_overriden_in_derived_type() - { - // Arrange - var wasCalled = false; - var action = new Func( - (AbstractBase @this) => - { - wasCalled = true; - return "Hello"; - }); - var shim = Shim - .Replace(() => Is.A().GetStringFromAbstractBase()) - .With(action); - - // Act - string dt = default; - wasCalled.Should().BeFalse(because: "no calls have been made yet"); - PoseContext.Isolate( - () => - { - var instance = new ShadowsMethodFromAbstractBase(); - dt = instance.GetStringFromAbstractBase(); - }, - shim - ); + // Assert + dt.Text.Should().BeEquivalentTo(nameof(Instance.Text), because: "that is what the shim is configured to return"); + } + } - // Assert - var _ = new ShadowsMethodFromAbstractBase(); - dt.Should().BeEquivalentTo(_.GetStringFromAbstractBase(), because: "the shim configured the base class"); - wasCalled.Should().BeFalse(because: "the shim was not invoked"); - } + public class ValueTypes + { + private struct Instance + { + public string Text { get; set; } + } - private sealed class SealedClass - { - public string SealedString { get; set; } = nameof(SealedString); + [Fact(Skip = "Not supported")] + public void Can_shim_constructor_of_value_type() + { + // Arrange + var action = new Func(() => new Instance(){Text = nameof(Instance.Text)}); + var shim = Shim + .Replace(() => new Instance()) + .With(action); + + // Act + Instance dt = default; + PoseContext.Isolate( + () => { dt = new Instance(); }, + shim + ); - public string GetSealedString() => nameof(GetSealedString); - } + // Assert + dt.Text.Should().BeEquivalentTo(nameof(Instance.Text), because: "that is what the shim is configured to return"); + } + } - [Fact] - public void Can_shim_method_of_sealed_class() - { - // Arrange - var action = new Func((SealedClass @this) => "String"); - var shim = Shim.Replace(() => Is.A().GetSealedString()).With(action); - - // Act - string dt = default; - PoseContext.Isolate( - () => - { - var instance = new SealedClass(); - dt = instance.GetSealedString(); - }, - shim - ); + public class SealedTypes + { + private sealed class Instance + { + public string Text { get; set; } + } + + [Fact] + public void Can_shim_constructor_of_sealed_reference_type() + { + // Arrange + var action = new Func(() => new Instance(){Text = nameof(Instance.Text)}); + var shim = Shim + .Replace(() => new Instance()) + .With(action); + + // Act + Instance dt = default; + PoseContext.Isolate( + () => { dt = new Instance(); }, + shim + ); - // Assert - dt.Should().BeEquivalentTo("String", because: "that is what the shim is configured to return"); + // Assert + dt.Text.Should().BeEquivalentTo(nameof(Instance.Text), because: "that is what the shim is configured to return"); + } - var sealedClass = new SealedClass(); - dt.Should().NotBeEquivalentTo(sealedClass.GetSealedString(), because: "that is the original value"); + } } - - [Fact] - public void Can_shim_property_getter_of_sealed_class() + + public class ShimSignatureValidation { - // Arrange - var action = new Func((SealedClass @this) => "String"); - var shim = Shim - .Replace(() => Is.A().SealedString) - .With(action); - - // Act - string dt = default; - PoseContext.Isolate( - () => - { - var instance = new SealedClass(); - dt = instance.SealedString; - }, - shim - ); - - // Assert - dt.Should().BeEquivalentTo("String", because: "that is what the shim is configured to return"); + private class Instance + { + public string GetString() => null; + } + + [Fact] + public void Throws_InvalidShimSignatureException_if_the_signature_of_the_replacement_does_not_match() + { + // Arrange + var shimTests = new Instance(); + + // Act + Action act = () => Shim.Replace(() => shimTests.GetString()).With(() => { }); // Targets Shim.Replace(Expression>) + Action act1 = () => Shim.Replace(() => Console.WriteLine(Is.A())).With(() => { }); // Targets Shim.Replace(Expression) + + // Assert + act.Should().Throw(because: "the signature of the replacement method does not match the original"); + act1.Should().Throw(because: "the signature of the replacement method does not match the original"); + } - var sealedClass = new SealedClass(); - dt.Should().NotBeEquivalentTo(sealedClass.SealedString, because: "that is the original value"); + [Fact] + public void Reports_types_when_throwing_InvalidShimSignatureException() + { + // Arrange + var shimTests = new Instance(); + + // Act + Action act = () => Shim.Replace(() => shimTests.GetString()).With(() => { }); // Targets Shim.Replace(Expression>) + Action act1 = () => Shim.Replace(() => Console.WriteLine(Is.A())).With(() => { }); // Targets Shim.Replace(Expression) + Action act2 = () => Shim.Replace(() => Is.A().Date).With((DateTime @this) => { return new DateTime(2004, 1, 1); }); // Targets Shim.Replace(Expression) + Action act3 = () => Shim.Replace(() => Is.A().Date).With((ref TimeSpan @this) => { return new DateTime(2004, 1, 1); }); // Targets Shim.Replace(Expression) + + // Assert + act.Should() + .Throw(because: "the signature of the replacement method does not match the original") + .WithMessage("*Expected System.String* Got System.Void"); + act1.Should() + .Throw(because: "the signature of the replacement method does not match the original") + .WithMessage("*Expected 1. Got 0*"); + act2.Should() + .Throw(because: "value types must be passed by ref") + .WithMessage("*ValueType instances must be passed by ref*"); + act3.Should() + .Throw(because: "the signature of the replacement method does not match the original") + .WithMessage("*Expected System.DateTime* Got System.TimeSpan*"); + } } - - [Fact] - public void Can_shim_property_setter_of_sealed_class() - { - // Arrange - var wasCalled = false; - var action = new Action((SealedClass @this, string value) => - { - wasCalled = true; - @this.SealedString = "Something"; - } - ); - var shim = Shim - .Replace(() => Is.A().SealedString, setter: true) - .With(action); - - // Act - wasCalled.Should().BeFalse(because: "no calls have been made yet"); - string dt = default; - PoseContext.Isolate( - () => - { - var instance = new SealedClass(); - instance.SealedString = "!!!"; - dt = instance.SealedString; - }, - shim - ); - - // Assert - wasCalled.Should().BeTrue(because: "the shim has been invoked"); - dt.Should().BeEquivalentTo("Something", because: "that is what the shim is configured to return"); - - var sealedClass = new SealedClass(); - dt.Should().NotBeEquivalentTo(sealedClass.SealedString, because: "that is the original value"); + + // [Fact] + // public void TestShimReplaceWith() + // { + // // Arrange + // var shimTests = new Instance(); + // var action = new Action(() => { }); + // var actionInstance = new Action(s => { }); + // + // // Act + // var shim = Shim.Replace(() => Console.WriteLine()).With(action); + // var shim1 = Shim.Replace(() => shimTests.VoidMethod()).With(actionInstance); + // + // // Assert + // var consoleWriteLineMethod = typeof(Console).GetMethod(nameof(Console.WriteLine), Type.EmptyTypes); + // shim.Original.Should().BeSameAs(consoleWriteLineMethod); + // shim.Replacement.Should().BeSameAs(action); + // + // var voidMethod = typeof(Instance).GetMethod(nameof(Instance.VoidMethod)); + // shim1.Original.Should().BeSameAs(voidMethod, because: "the shim is configured for this method"); + // shim1.Instance.Should().BeSameAs(shimTests, because: "the shim is configured for this instance"); + // shim1.Replacement.Should().BeSameAs(actionInstance, because: "that is the shim's replacement"); + // } + + public class Legacy + { + [Fact] + public void TestReplacePropertyGetter() + { + var shim = Shim.Replace(() => Thread.CurrentThread.CurrentCulture); + + var currentCultureGetMethod = typeof(Thread).GetProperty(nameof(Thread.CurrentCulture), typeof(CultureInfo)).GetMethod; + shim.Original.Should().BeEquivalentTo(currentCultureGetMethod, because: "the shim configures that method"); + shim.Replacement.Should().BeNull(because: "no replacement is configured"); + } + + [Fact] + public void TestReplacePropertySetter() + { + // Arrange + var shim = Shim.Replace(() => Is.A().CurrentCulture, true); + + var currentCultureSetMethod = typeof(Thread).GetProperty(nameof(Thread.CurrentCulture), typeof(CultureInfo)).SetMethod; + shim.Original.Should().BeEquivalentTo(currentCultureSetMethod, because: "the shim configures that method"); + shim.Replacement.Should().BeNull(because: "no replacement is configured"); + } + + [Fact] + public void TestReplacePropertySetterAction() + { + // Arrange + var getterExecuted = false; + var getterShim = Shim + .Replace(() => Is.A().CurrentCulture) + .With( + (Thread t) => + { + getterExecuted = true; + return t.CurrentCulture; + } + ); + + var setterExecuted = false; + var setterShim = Shim + .Replace(() => Is.A().CurrentCulture, true) + .With( + (Thread t, CultureInfo value) => + { + setterExecuted = true; + t.CurrentCulture = value; + } + ); + + // Pre-Act asserts + var currentCultureProperty = typeof(Thread).GetProperty(nameof(Thread.CurrentCulture), typeof(CultureInfo)); + getterShim.Original.Should().BeEquivalentTo(currentCultureProperty.GetMethod, because: "the shim configures that method"); + setterShim.Original.Should().BeEquivalentTo(currentCultureProperty.SetMethod, because: "the shim configures that method"); + + // Act + PoseContext.Isolate( + () => + { + var oldCulture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = oldCulture; + }, + getterShim, + setterShim + ); + + // Assert + getterExecuted.Should().BeTrue(because: "the shim was executed"); + setterExecuted.Should().BeTrue(because: "the shim was executed"); + } } } }