From 66c66da13345609ecbdd415ecc6df7bbb469a35b Mon Sep 17 00:00:00 2001 From: Martijn Bodeman <11424653+skwasjer@users.noreply.github.com> Date: Sat, 18 Nov 2023 07:39:53 +0100 Subject: [PATCH] test: replace Moq with NSubstitute (#85) --- test/Directory.Build.targets | 4 +- .../MockHttpRequestContextExtensionsTests.cs | 2 +- .../JsonContentMatcherTests.cs | 78 +++++++--------- .../MockConfigurationExtensionsTests.cs | 13 +-- .../MockHttp.Tests/AnyRequestMatchingTests.cs | 17 ++-- .../Extensions/IRespondsExtensionsTests.cs | 14 ++- .../Extensions/ListExtensionsTests.cs | 4 +- .../RequestMatchingExtensionsTests.cs | 21 +++-- .../Response/FuncStreamBodyCannotReadSpec.cs | 11 +-- .../Flow/Response/NullBuilderTests.cs | 10 +-- .../Flow/Response/StreamBodyCannotReadSpec.cs | 11 +-- .../Flow/Response/StreamBodyCannotSeekSpec.cs | 13 ++- .../Matchers/AnyMatcherTests.cs | 10 +-- .../Matchers/FakeToStringTestMatcher.cs | 20 +++++ .../Matchers/FormDataMatcherTests.cs | 88 ++++++++----------- .../Matchers/HttpRequestMatcherTests.cs | 7 +- .../Matchers/NotMatcherTests.cs | 17 ++-- .../MockPriorityBehaviorTests.cs | 26 ------ test/MockHttp.Tests/RateLimitedStreamTests.cs | 30 +++---- test/MockHttp.Tests/RequestMatchingTests.cs | 17 ++-- .../Responses/EmptyContentTests.cs | 9 +- .../Responses/TimeoutBehaviorTests.cs | 12 +-- 22 files changed, 182 insertions(+), 252 deletions(-) create mode 100644 test/MockHttp.Tests/Matchers/FakeToStringTestMatcher.cs diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets index f4f4b349..a86e5b8f 100644 --- a/test/Directory.Build.targets +++ b/test/Directory.Build.targets @@ -13,13 +13,13 @@ - + - + diff --git a/test/MockHttp.Json.Tests/Extensions/MockHttpRequestContextExtensionsTests.cs b/test/MockHttp.Json.Tests/Extensions/MockHttpRequestContextExtensionsTests.cs index ce8a6ba4..8d1f0f01 100644 --- a/test/MockHttp.Json.Tests/Extensions/MockHttpRequestContextExtensionsTests.cs +++ b/test/MockHttp.Json.Tests/Extensions/MockHttpRequestContextExtensionsTests.cs @@ -26,7 +26,7 @@ public void Given_that_adapter_is_not_registered_when_getting_adapter_it_should_ public void Given_that_adapter_is_registered_when_getting_adapter_it_should_return_same_instance() { using var msg = new HttpRequestMessage(); - IJsonAdapter adapter = Mock.Of(); + IJsonAdapter adapter = Substitute.For(); var items = new Dictionary { { typeof(IJsonAdapter), adapter } }; var sut = new MockHttpRequestContext(msg, items); sut.Services.Should().NotBeEmpty(); diff --git a/test/MockHttp.Json.Tests/JsonContentMatcherTests.cs b/test/MockHttp.Json.Tests/JsonContentMatcherTests.cs index bde5fb06..dc1ba740 100644 --- a/test/MockHttp.Json.Tests/JsonContentMatcherTests.cs +++ b/test/MockHttp.Json.Tests/JsonContentMatcherTests.cs @@ -4,22 +4,21 @@ namespace MockHttp.Json; public sealed class JsonContentMatcherTests : IDisposable { - private readonly Mock _adapterMock; - private readonly Mock> _equalityComparerMock; - private readonly Dictionary _services; + private readonly IJsonAdapter _adapterMock; + private readonly IEqualityComparer _equalityComparerMock; + private readonly Dictionary _services = new(); private readonly HttpRequestMessage _requestMessage; private readonly MockHttpRequestContext _requestContext; public JsonContentMatcherTests() { - _adapterMock = new Mock(); + _adapterMock = Substitute.For(); - _equalityComparerMock = new Mock>(); + _equalityComparerMock = Substitute.For>(); _equalityComparerMock - .Setup(m => m.Equals(It.IsAny()!, It.IsAny()!)) + .Equals(Arg.Any(), Arg.Any()) .Returns(true); - _services = new Dictionary(); _requestMessage = new HttpRequestMessage(); _requestContext = new MockHttpRequestContext(_requestMessage, _services); } @@ -30,25 +29,21 @@ public async Task Given_that_adapter_is_provided_to_ctor_when_matching_it_should var jsonContentAsObject = new { PropertyName = "value" }; const string serializedJson = "{\"modifiedPropertyName\":\"modifiedValue\"}"; _adapterMock - .Setup(m => m.Serialize(jsonContentAsObject)) - .Returns(serializedJson) - .Verifiable(); + .Serialize(Arg.Any()) + .Returns(serializedJson); - var globalAdapterMock = new Mock(); - _services.Add(typeof(IJsonAdapter), globalAdapterMock.Object); + IJsonAdapter globalAdapterMock = Substitute.For(); + _services.Add(typeof(IJsonAdapter), globalAdapterMock); - var sut = new JsonContentMatcher(jsonContentAsObject, _adapterMock.Object, _equalityComparerMock.Object); + var sut = new JsonContentMatcher(jsonContentAsObject, _adapterMock, _equalityComparerMock); // Act await sut.IsMatchAsync(_requestContext); // Assert - _adapterMock.Verify(); - globalAdapterMock.Verify(m => m.Serialize(It.IsAny()), Times.Never); - _equalityComparerMock - .Verify(m => m.Equals(It.IsAny(), serializedJson), - Times.Once - ); + _adapterMock.Received(1).Serialize(jsonContentAsObject); + globalAdapterMock.DidNotReceiveWithAnyArgs().Serialize(Arg.Any()); + _ = _equalityComparerMock.Received(1).Equals(Arg.Any(), serializedJson); } [Fact] @@ -57,24 +52,20 @@ public async Task Given_that_adapter_is_not_provided_to_ctor_when_matching_it_sh var jsonContentAsObject = new { PropertyName = "value" }; const string serializedJson = "{\"modifiedPropertyName\":\"modifiedValue\"}"; - var globalAdapterMock = new Mock(); + IJsonAdapter globalAdapterMock = Substitute.For(); globalAdapterMock - .Setup(m => m.Serialize(jsonContentAsObject)) - .Returns(serializedJson) - .Verifiable(); + .Serialize(Arg.Any()) + .Returns(serializedJson); - var sut = new JsonContentMatcher(jsonContentAsObject, null, _equalityComparerMock.Object); + var sut = new JsonContentMatcher(jsonContentAsObject, null, _equalityComparerMock); // Act - _services.Add(typeof(IJsonAdapter), globalAdapterMock.Object); + _services.Add(typeof(IJsonAdapter), globalAdapterMock); await sut.IsMatchAsync(_requestContext); // Assert - globalAdapterMock.Verify(); - _equalityComparerMock - .Verify(m => m.Equals(It.IsAny(), serializedJson), - Times.Once - ); + globalAdapterMock.Received(1).Serialize(jsonContentAsObject); + _ = _equalityComparerMock.Received(1).Equals(Arg.Any(), serializedJson); } [Fact] @@ -82,30 +73,27 @@ public async Task Given_that_adapter_is_not_provided_to_ctor_and_no_global_adapt { var jsonContentAsObject = new { PropertyName = "value" }; const string serializedJson = "{\"PropertyName\":\"value\"}"; - var sut = new JsonContentMatcher(jsonContentAsObject, null, _equalityComparerMock.Object); + var sut = new JsonContentMatcher(jsonContentAsObject, null, _equalityComparerMock); // Act _services.Should().NotContainKey(typeof(IJsonAdapter)); await sut.IsMatchAsync(_requestContext); // Assert - _equalityComparerMock - .Verify(m => m.Equals(It.IsAny(), serializedJson), - Times.Once - ); + _ = _equalityComparerMock.Received(1).Equals(Arg.Any(), serializedJson); } [Fact] public async Task Given_that_adapter_is_not_provided_to_ctor_when_matching_it_should_use_the_adapter() { const string jsonContentAsObject = "text"; - var sut = new JsonContentMatcher(jsonContentAsObject, _adapterMock.Object, _equalityComparerMock.Object); + var sut = new JsonContentMatcher(jsonContentAsObject, _adapterMock, _equalityComparerMock); // Act await sut.IsMatchAsync(_requestContext); // Assert - _adapterMock.Verify(m => m.Serialize(jsonContentAsObject), Times.Once); + _adapterMock.Received(1).Serialize(jsonContentAsObject); } [Theory] @@ -113,18 +101,17 @@ public async Task Given_that_adapter_is_not_provided_to_ctor_when_matching_it_sh [InlineData(false)] public async Task When_matching_it_should_return_the_results_of_the_comparer(bool equals) { - var sut = new JsonContentMatcher("something to compare with", _adapterMock.Object, _equalityComparerMock.Object); + var sut = new JsonContentMatcher("something to compare with", _adapterMock, _equalityComparerMock); _equalityComparerMock - .Setup(m => m.Equals(It.IsAny()!, It.IsAny()!)) - .Returns(equals) - .Verifiable(); + .Equals(Arg.Any(), Arg.Any()) + .Returns(equals); // Act bool actual = await sut.IsMatchAsync(_requestContext); // Assert - _equalityComparerMock.Verify(); + _ =_equalityComparerMock.Received(1).Equals(Arg.Any(), Arg.Any()); actual.Should().Be(equals); } @@ -137,17 +124,14 @@ HttpContent content { using (content) { - var sut = new JsonContentMatcher("something to compare with", _adapterMock.Object, _equalityComparerMock.Object); + var sut = new JsonContentMatcher("something to compare with", _adapterMock, _equalityComparerMock); _requestMessage.Content = content; // Act await sut.IsMatchAsync(_requestContext); // Assert - _equalityComparerMock - .Verify(m => m.Equals(string.Empty, It.IsAny()), - Times.Once - ); + _ = _equalityComparerMock.Received(1).Equals(string.Empty, Arg.Any()); } } diff --git a/test/MockHttp.Json.Tests/MockConfigurationExtensionsTests.cs b/test/MockHttp.Json.Tests/MockConfigurationExtensionsTests.cs index 6f4647f8..f9801147 100644 --- a/test/MockHttp.Json.Tests/MockConfigurationExtensionsTests.cs +++ b/test/MockHttp.Json.Tests/MockConfigurationExtensionsTests.cs @@ -2,12 +2,7 @@ public class MockConfigurationExtensionsTests { - private readonly MockHttpHandler _sut; - - public MockConfigurationExtensionsTests() - { - _sut = new MockHttpHandler(); - } + private readonly MockHttpHandler _sut = new(); [Theory] [MemberData(nameof(UseJsonAdapterNullTestCases))] @@ -29,8 +24,8 @@ public void Given_that_arg_is_null_when_creating_instance_using_it_should_throw public static IEnumerable UseJsonAdapterNullTestCases() { - IMockConfiguration mockConfig = Mock.Of(); - IJsonAdapter jsonAdapter = Mock.Of(); + IMockConfiguration mockConfig = Substitute.For(); + IJsonAdapter jsonAdapter = Substitute.For(); yield return new object?[] { null, jsonAdapter, nameof(mockConfig) }; yield return new object?[] { mockConfig, null, nameof(jsonAdapter) }; @@ -39,7 +34,7 @@ public void Given_that_arg_is_null_when_creating_instance_using_it_should_throw [Fact] public void When_using_it_should_register_adapter() { - IJsonAdapter adapter = Mock.Of(); + IJsonAdapter adapter = Substitute.For(); // Act _sut.UseJsonAdapter(adapter); diff --git a/test/MockHttp.Tests/AnyRequestMatchingTests.cs b/test/MockHttp.Tests/AnyRequestMatchingTests.cs index 22ac808c..21995f62 100644 --- a/test/MockHttp.Tests/AnyRequestMatchingTests.cs +++ b/test/MockHttp.Tests/AnyRequestMatchingTests.cs @@ -12,19 +12,18 @@ public class AnyRequestMatchingTests public AnyRequestMatchingTests() { - static HttpRequestMatcher CreateMatcherMock(Func returns) - { - var matcherMock = new Mock(); - matcherMock - .Setup(m => m.IsExclusive) - .Returns(returns); - return matcherMock.Object; - } - _sut = new AnyRequestMatching(); _matcher1 = CreateMatcherMock(() => _isExclusive1); _matcher2 = CreateMatcherMock(() => _isExclusive2); + return; + + static HttpRequestMatcher CreateMatcherMock(Func returns) + { + HttpRequestMatcher matcherMock = Substitute.For(); + matcherMock.IsExclusive.Returns(_ => returns()); + return matcherMock; + } } [Fact] diff --git a/test/MockHttp.Tests/Extensions/IRespondsExtensionsTests.cs b/test/MockHttp.Tests/Extensions/IRespondsExtensionsTests.cs index f624665c..119b617a 100644 --- a/test/MockHttp.Tests/Extensions/IRespondsExtensionsTests.cs +++ b/test/MockHttp.Tests/Extensions/IRespondsExtensionsTests.cs @@ -54,11 +54,12 @@ public class WithStream : IRespondsExtensionsTests [InlineData(false)] public async Task When_responding_with_stream_it_should_return_response(bool isSeekable) { - using var ms = new CanSeekMemoryStream(Encoding.UTF8.GetBytes("content"), isSeekable); + using var ms = new CanSeekMemoryStream("content"u8.ToArray(), isSeekable); var request = new HttpRequestMessage(); - var expectedContent = new ByteArrayContent(Encoding.UTF8.GetBytes("content")); + var expectedContent = new ByteArrayContent("content"u8.ToArray()); // Act + // ReSharper disable once AccessToDisposedClosure _sut.Respond(with => with.Body(ms)); HttpResponseMessage actualResponse = await _httpCall.SendAsync(new MockHttpRequestContext(request), CancellationToken.None); @@ -90,9 +91,8 @@ public void When_responding_with_null_stream_it_should_throw() [Fact] public void When_responding_with_not_readable_stream_it_should_throw() { - var streamMock = new Mock(); - streamMock.Setup(m => m.CanRead).Returns(false); - Stream? streamContent = streamMock.Object; + Stream streamContent = Substitute.For(); + streamContent.CanRead.Returns(false); // Act Action act = () => _sut.Respond(with => with.Body(streamContent)); @@ -199,10 +199,8 @@ public void Given_null_argument_when_executing_method_it_should_throw(params obj public static IEnumerable TestCases() { - var streamMock = new Mock { CallBase = true }; - streamMock.SetReturnsDefault(true); using var content = new StringContent(""); - IResponds responds = Mock.Of>(); + IResponds responds = Substitute.For>(); DelegateTestCase[] testCases = { diff --git a/test/MockHttp.Tests/Extensions/ListExtensionsTests.cs b/test/MockHttp.Tests/Extensions/ListExtensionsTests.cs index e2da9899..b1ec79fb 100644 --- a/test/MockHttp.Tests/Extensions/ListExtensionsTests.cs +++ b/test/MockHttp.Tests/Extensions/ListExtensionsTests.cs @@ -45,11 +45,11 @@ public void Given_that_list_contains_instances_of_type_when_getting_index_it_sho { var list = new List { - Mock.Of(), + Substitute.For(), null, new Foo(), new Foo(), - Mock.Of(), + Substitute.For(), new FooBar(), new Foo(), new FooBar() diff --git a/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs b/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs index e3a010c0..4ba231e0 100644 --- a/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs +++ b/test/MockHttp.Tests/Extensions/RequestMatchingExtensionsTests.cs @@ -292,7 +292,7 @@ public async Task When_configuring_formData_should_match(string urlEncodedFormDa var request = new HttpRequestMessage { RequestUri = new Uri("http://127.0.0.1"), - Content = new ByteArrayContent(Encoding.UTF8.GetBytes("key1=value1&key2=value2")) + Content = new ByteArrayContent("key1=value1&key2=value2"u8.ToArray()) { Headers = { @@ -319,8 +319,8 @@ public async Task When_configuring_formData_should_match_multipart(string urlEnc { var content = new MultipartFormDataContent { - { new ByteArrayContent(Encoding.UTF8.GetBytes("value1")), "key1" }, - { new ByteArrayContent(Encoding.UTF8.GetBytes("éôxÄ")), "key2" }, + { new ByteArrayContent("value1"u8.ToArray()), "key1" }, + { new ByteArrayContent("éôxÄ"u8.ToArray()), "key2" }, { new StringContent("file content 1", Encoding.UTF8, MediaTypes.PlainText), "file1", "file1.txt" } }; var request = new HttpRequestMessage @@ -679,8 +679,11 @@ public void Given_null_argument_when_executing_method_it_should_throw(params obj public static IEnumerable TestCases() { - var streamMock = new Mock { CallBase = true }; - streamMock.SetReturnsDefault(true); + Stream streamMock = Substitute.For(); + streamMock.CanRead.Returns(true); + streamMock.CanWrite.Returns(true); + streamMock.CanSeek.Returns(true); + streamMock.CanTimeout.Returns(true); var uri = new Uri("http://0.0.0.0"); var instance = new RequestMatching(); @@ -830,11 +833,11 @@ public void Given_null_argument_when_executing_method_it_should_throw(params obj DelegateTestCase.Create( RequestMatchingExtensions.Body, instance, - Encoding.UTF8.GetBytes("content")), + "content"u8.ToArray()), DelegateTestCase.Create( RequestMatchingExtensions.Body, instance, - streamMock.Object), + streamMock), DelegateTestCase.Create( RequestMatchingExtensions.WithoutBody, instance), @@ -850,11 +853,11 @@ public void Given_null_argument_when_executing_method_it_should_throw(params obj DelegateTestCase.Create( RequestMatchingExtensions.PartialBody, instance, - Encoding.UTF8.GetBytes("partial content")), + "partial content"u8.ToArray()), DelegateTestCase.Create( RequestMatchingExtensions.PartialBody, instance, - streamMock.Object), + streamMock), DelegateTestCase.Create, RequestMatching>( RequestMatchingExtensions.Any, instance, diff --git a/test/MockHttp.Tests/Language/Flow/Response/FuncStreamBodyCannotReadSpec.cs b/test/MockHttp.Tests/Language/Flow/Response/FuncStreamBodyCannotReadSpec.cs index 880145ea..bdc68be2 100644 --- a/test/MockHttp.Tests/Language/Flow/Response/FuncStreamBodyCannotReadSpec.cs +++ b/test/MockHttp.Tests/Language/Flow/Response/FuncStreamBodyCannotReadSpec.cs @@ -4,16 +4,13 @@ namespace MockHttp.Language.Flow.Response; public class FuncStreamBodyCannotReadSpec : GuardedResponseSpec { - private readonly Mock _streamMock = new(); + private readonly Stream _streamMock = Substitute.For(); protected override void Given(IResponseBuilder with) { - _streamMock - .Setup(m => m.CanRead) - .Returns(false) - .Verifiable(); + _streamMock.CanRead.Returns(false); - with.Body(() => _streamMock.Object); + with.Body(() => _streamMock); } protected override async Task ShouldThrow(Func act) @@ -21,6 +18,6 @@ protected override async Task ShouldThrow(Func act) await act.Should() .ThrowExactlyAsync() .WithMessage("Cannot read from stream.*"); - _streamMock.Verify(); + _ = _streamMock.Received(1).CanRead; } } diff --git a/test/MockHttp.Tests/Language/Flow/Response/NullBuilderTests.cs b/test/MockHttp.Tests/Language/Flow/Response/NullBuilderTests.cs index 881cd092..12e6ac26 100644 --- a/test/MockHttp.Tests/Language/Flow/Response/NullBuilderTests.cs +++ b/test/MockHttp.Tests/Language/Flow/Response/NullBuilderTests.cs @@ -19,11 +19,11 @@ public void Given_null_argument_when_executing_method_it_should_throw(params obj public static IEnumerable TestCases() { var responseBuilderImpl = new ResponseBuilder(); - IResponseBuilder responseBuilder = Mock.Of(); - IWithContent withContent = Mock.Of(); - IWithStatusCode withStatusCode = Mock.Of(); - IWithContentType withContentType = Mock.Of(); - IWithHeaders withHeaders = Mock.Of(); + IResponseBuilder responseBuilder = Substitute.For(); + IWithContent withContent = Substitute.For(); + IWithStatusCode withStatusCode = Substitute.For(); + IWithContentType withContentType = Substitute.For(); + IWithHeaders withHeaders = Substitute.For(); DelegateTestCase[] testCases = { diff --git a/test/MockHttp.Tests/Language/Flow/Response/StreamBodyCannotReadSpec.cs b/test/MockHttp.Tests/Language/Flow/Response/StreamBodyCannotReadSpec.cs index c4b3e84f..2fc68528 100644 --- a/test/MockHttp.Tests/Language/Flow/Response/StreamBodyCannotReadSpec.cs +++ b/test/MockHttp.Tests/Language/Flow/Response/StreamBodyCannotReadSpec.cs @@ -4,16 +4,13 @@ namespace MockHttp.Language.Flow.Response; public class StreamBodyCannotReadSpec : GuardedResponseSpec { - private readonly Mock _streamMock = new(); + private readonly Stream _streamMock = Substitute.For(); protected override void Given(IResponseBuilder with) { - _streamMock - .Setup(m => m.CanRead) - .Returns(false) - .Verifiable(); + _streamMock.CanRead.Returns(false); - with.Body(_streamMock.Object); + with.Body(_streamMock); } protected override async Task ShouldThrow(Func act) @@ -22,6 +19,6 @@ await act.Should() .ThrowExactlyAsync() .WithParameterName("streamContent") .WithMessage("Cannot read from stream.*"); - _streamMock.Verify(); + _ = _streamMock.Received(1).CanRead; } } diff --git a/test/MockHttp.Tests/Language/Flow/Response/StreamBodyCannotSeekSpec.cs b/test/MockHttp.Tests/Language/Flow/Response/StreamBodyCannotSeekSpec.cs index ccae9b92..1e247d48 100644 --- a/test/MockHttp.Tests/Language/Flow/Response/StreamBodyCannotSeekSpec.cs +++ b/test/MockHttp.Tests/Language/Flow/Response/StreamBodyCannotSeekSpec.cs @@ -2,26 +2,23 @@ public class StreamBodyCannotSeekSpec : StreamBodySpec { - private Mock? _streamMock; + private MemoryStream? _streamMock; protected override Stream CreateStream() { - _streamMock = new Mock(Content) { CallBase = true }; - _streamMock - .Setup(s => s.CanSeek) - .Returns(false); - return _streamMock.Object; + _streamMock = Substitute.ForPartsOf(Content, false); + return _streamMock; } protected override Task Should(HttpResponseMessage response) { - _streamMock?.Verify(); + _ = _streamMock!.Received().CanSeek; return base.Should(response); } public override Task DisposeAsync() { - _streamMock?.Object.Dispose(); + _streamMock?.Dispose(); return base.DisposeAsync(); } } diff --git a/test/MockHttp.Tests/Matchers/AnyMatcherTests.cs b/test/MockHttp.Tests/Matchers/AnyMatcherTests.cs index cda372f1..2491861a 100644 --- a/test/MockHttp.Tests/Matchers/AnyMatcherTests.cs +++ b/test/MockHttp.Tests/Matchers/AnyMatcherTests.cs @@ -62,12 +62,10 @@ public void When_formatting_should_return_human_readable_representation() { const string innerMatcherText = "Type: text"; string expectedText = string.Format(@"Any:{1}{{{1} {0}1{1} {0}2{1}}}", innerMatcherText, Environment.NewLine); - var matcherMock1 = new Mock(); - var matcherMock2 = new Mock(); - matcherMock1.Setup(m => m.ToString()).Returns(innerMatcherText + "1"); - matcherMock2.Setup(m => m.ToString()).Returns(innerMatcherText + "2"); - _matchers.Add(matcherMock1.Object); - _matchers.Add(matcherMock2.Object); + var matcherMock1 = new FakeToStringTestMatcher(innerMatcherText + "1"); + var matcherMock2 = new FakeToStringTestMatcher(innerMatcherText + "2"); + _matchers.Add(matcherMock1); + _matchers.Add(matcherMock2); // Act string displayText = _sut.ToString(); diff --git a/test/MockHttp.Tests/Matchers/FakeToStringTestMatcher.cs b/test/MockHttp.Tests/Matchers/FakeToStringTestMatcher.cs new file mode 100644 index 00000000..0a4660e5 --- /dev/null +++ b/test/MockHttp.Tests/Matchers/FakeToStringTestMatcher.cs @@ -0,0 +1,20 @@ +using MockHttp.Responses; + +namespace MockHttp.Matchers; + +internal sealed class FakeToStringTestMatcher : HttpRequestMatcher +{ + private readonly string _matcherName; + + public FakeToStringTestMatcher(string matcherName) + { + _matcherName = matcherName; + } + + public override bool IsMatch(MockHttpRequestContext requestContext) + { + throw new NotImplementedException(); + } + + public override string ToString() => _matcherName; +} diff --git a/test/MockHttp.Tests/Matchers/FormDataMatcherTests.cs b/test/MockHttp.Tests/Matchers/FormDataMatcherTests.cs index ffde4f22..8844b36b 100644 --- a/test/MockHttp.Tests/Matchers/FormDataMatcherTests.cs +++ b/test/MockHttp.Tests/Matchers/FormDataMatcherTests.cs @@ -16,15 +16,13 @@ public class FormDataMatcherTests [InlineData("key1=value1&%C3%A9%C3%B4x%C3%84=%24%25%5E%26*&key2=value", "éôxÄ", "$%^&*")] public async Task Given_formData_equals_expected_formData_when_matching_should_match(string formData, string expectedKey, string? expectedValue) { - using var request = new HttpRequestMessage + using var request = new HttpRequestMessage(); + request.RequestUri = new Uri("http://localhost/" + formData); + request.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes(formData))) { - RequestUri = new Uri("http://localhost/" + formData), - Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes(formData))) + Headers = { - Headers = - { - ContentType = new MediaTypeHeaderValue(MediaTypes.FormUrlEncoded) - } + ContentType = new MediaTypeHeaderValue(MediaTypes.FormUrlEncoded) } }; @@ -48,15 +46,13 @@ expectedValue is null [InlineData("key=value1&key=value2")] public async Task Given_formData_does_not_equal_expected_formData_when_matching_should_not_match(string formData) { - using var request = new HttpRequestMessage - { - RequestUri = new Uri("http://localhost/"), - Content = new StringContent( - formData, - Encoding.UTF8, - MediaTypes.FormUrlEncoded - ) - }; + using var request = new HttpRequestMessage(); + request.RequestUri = new Uri("http://localhost/"); + request.Content = new StringContent( + formData, + Encoding.UTF8, + MediaTypes.FormUrlEncoded + ); var sut = new FormDataMatcher(new[] { new KeyValuePair>("key_not_in_formdata", null!) }); @@ -69,15 +65,13 @@ public async Task Given_formData_and_no_expected_formData_when_matching_should_n { var sut = new FormDataMatcher(""); - using var request = new HttpRequestMessage - { - RequestUri = new Uri("http://localhost/"), - Content = new StringContent( - "unexpected=formdata", - Encoding.UTF8, - MediaTypes.FormUrlEncoded - ) - }; + using var request = new HttpRequestMessage(); + request.RequestUri = new Uri("http://localhost/"); + request.Content = new StringContent( + "unexpected=formdata", + Encoding.UTF8, + MediaTypes.FormUrlEncoded + ); // Act & assert (await sut.IsMatchAsync(new MockHttpRequestContext(request))).Should().BeFalse("no form data was expected"); @@ -123,18 +117,14 @@ public void When_formatting_should_return_human_readable_representation() [Fact] public async Task Given_multiPart_formData_equals_expected_formData_when_matching_should_match() { - using var content = new MultipartFormDataContent - { - { new ByteArrayContent(Encoding.UTF8.GetBytes("value1")), "key1" }, - { new ByteArrayContent(Encoding.UTF8.GetBytes("éôxÄ")), "key2" }, - { new StringContent("file content 1", Encoding.UTF8, MediaTypes.PlainText), "file1", "file1.txt" } - }; + using var content = new MultipartFormDataContent(); + content.Add(new ByteArrayContent("value1"u8.ToArray()), "key1"); + content.Add(new ByteArrayContent("éôxÄ"u8.ToArray()), "key2"); + content.Add(new StringContent("file content 1", Encoding.UTF8, MediaTypes.PlainText), "file1", "file1.txt"); - using var request = new HttpRequestMessage - { - RequestUri = new Uri("http://localhost/"), - Content = content - }; + using var request = new HttpRequestMessage(); + request.RequestUri = new Uri("http://localhost/"); + request.Content = content; var sut = new FormDataMatcher(new[] { @@ -149,18 +139,14 @@ public async Task Given_multiPart_formData_equals_expected_formData_when_matchin [Fact] public async Task Given_multiPart_formData_does_not_equal_expected_formData_when_matching_should_not_match() { - using var content = new MultipartFormDataContent - { - { new ByteArrayContent(Encoding.UTF8.GetBytes("value1")), "key1" }, - { new ByteArrayContent(Encoding.UTF8.GetBytes("éôxÄ")), "key2" }, - { new StringContent("file content 1", Encoding.UTF8, MediaTypes.PlainText), "file1", "file1.txt" } - }; + using var content = new MultipartFormDataContent(); + content.Add(new ByteArrayContent("value1"u8.ToArray()), "key1"); + content.Add(new ByteArrayContent("éôxÄ"u8.ToArray()), "key2"); + content.Add(new StringContent("file content 1", Encoding.UTF8, MediaTypes.PlainText), "file1", "file1.txt"); - using var request = new HttpRequestMessage - { - RequestUri = new Uri("http://localhost/"), - Content = content - }; + using var request = new HttpRequestMessage(); + request.RequestUri = new Uri("http://localhost/"); + request.Content = content; var sut = new FormDataMatcher(new[] { @@ -190,11 +176,9 @@ await act.Should() [MemberData(nameof(NonFormDataContentTestCases))] public async Task Given_that_content_is_not_formData_when_matching_it_should_return_false(HttpContent content) { - using var request = new HttpRequestMessage - { - RequestUri = new Uri("http://localhost/"), - Content = content - }; + using var request = new HttpRequestMessage(); + request.RequestUri = new Uri("http://localhost/"); + request.Content = content; var sut = new FormDataMatcher(new[] { diff --git a/test/MockHttp.Tests/Matchers/HttpRequestMatcherTests.cs b/test/MockHttp.Tests/Matchers/HttpRequestMatcherTests.cs index 54169154..d464ddd1 100644 --- a/test/MockHttp.Tests/Matchers/HttpRequestMatcherTests.cs +++ b/test/MockHttp.Tests/Matchers/HttpRequestMatcherTests.cs @@ -4,12 +4,7 @@ namespace MockHttp.Matchers; public class HttpRequestMatcherTests { - private readonly HttpRequestMatcher _sut; - - public HttpRequestMatcherTests() - { - _sut = new Mock { CallBase = true }.Object; - } + private readonly HttpRequestMatcher _sut = Substitute.For(); [Fact] public async Task Given_null_context_when_matching_it_should_throw() diff --git a/test/MockHttp.Tests/Matchers/NotMatcherTests.cs b/test/MockHttp.Tests/Matchers/NotMatcherTests.cs index b26f2eb7..342130f7 100644 --- a/test/MockHttp.Tests/Matchers/NotMatcherTests.cs +++ b/test/MockHttp.Tests/Matchers/NotMatcherTests.cs @@ -4,13 +4,13 @@ namespace MockHttp.Matchers; public class NotMatcherTests { - private readonly Mock _innerMatcherMock; + private readonly IAsyncHttpRequestMatcher _innerMatcherMock; private readonly NotMatcher _sut; public NotMatcherTests() { - _innerMatcherMock = new Mock(); - _sut = new NotMatcher(_innerMatcherMock.Object); + _innerMatcherMock = Substitute.For(); + _sut = new NotMatcher(_innerMatcherMock); } [Theory] @@ -20,13 +20,14 @@ public async Task Given_inner_matcher_when_matching_it_should_return_inverse(boo { var request = new HttpRequestMessage(HttpMethod.Get, "http://0.0.0.0/url"); _innerMatcherMock - .Setup(m => m.IsMatchAsync(It.IsAny())) - .ReturnsAsync(innerMatchResult); + .IsMatchAsync(Arg.Any()) + .Returns(Task.FromResult(innerMatchResult)); // Act & assert (await _sut.IsMatchAsync(new MockHttpRequestContext(request))) .Should() .Be(!innerMatchResult); + await _innerMatcherMock.Received(1).IsMatchAsync(Arg.Any()); } [Fact] @@ -47,11 +48,11 @@ public void Given_null_inner_matcher_when_creating_matcher_should_throw() public void When_formatting_should_return_human_readable_representation() { const string innerMatcherText = "Type: text"; - string expectedText = $"Not {innerMatcherText}"; - _innerMatcherMock.Setup(m => m.ToString()).Returns(innerMatcherText); + const string expectedText = $"Not {innerMatcherText}"; + var sut = new NotMatcher(new FakeToStringTestMatcher(innerMatcherText)); // Act - string displayText = _sut.ToString(); + string displayText = sut.ToString(); // Assert displayText.Should().Be(expectedText); diff --git a/test/MockHttp.Tests/MockPriorityBehaviorTests.cs b/test/MockHttp.Tests/MockPriorityBehaviorTests.cs index a5602e52..94f6bef9 100644 --- a/test/MockHttp.Tests/MockPriorityBehaviorTests.cs +++ b/test/MockHttp.Tests/MockPriorityBehaviorTests.cs @@ -29,30 +29,4 @@ public async Task Given_request_is_setup_twice_when_sending_request_last_setup_w response2.Should().HaveStatusCode(HttpStatusCode.Accepted, "the second setup wins on second request"); response3.Should().HaveStatusCode(HttpStatusCode.BadRequest, "the request was sent with different HTTP method matching third setup"); } - - /// - /// Asserting behavior of Moq that last setup wins, unless parameters are different. - /// We want the same behavior. - /// - [Fact] - public void Given_moq_is_setup_twice_when_calling_mocked_method_last_setup_wins() - { - var mock = new Mock(); - mock.Setup(m => m.GetResult(1)).Returns(1); - mock.Setup(m => m.GetResult(1)).Returns(2); - mock.Setup(m => m.GetResult(It.IsAny())).Returns(3); - mock.Setup(m => m.GetResult(2)).Returns(4); - - // Act - mock.Object.GetResult(1).Should().Be(3, "the third setup wins on first call"); - mock.Object.GetResult(1).Should().Be(3, "the third setup wins on second call"); - - // But of course, different params do give different result. - mock.Object.GetResult(2).Should().Be(4, "the method was called with different param matching fourth setup"); - } - - public interface ITest - { - object GetResult(int param); - } } diff --git a/test/MockHttp.Tests/RateLimitedStreamTests.cs b/test/MockHttp.Tests/RateLimitedStreamTests.cs index 77b412dd..0d941c67 100644 --- a/test/MockHttp.Tests/RateLimitedStreamTests.cs +++ b/test/MockHttp.Tests/RateLimitedStreamTests.cs @@ -1,6 +1,5 @@ using System.Diagnostics; using MockHttp.IO; -using Moq.Protected; using Xunit.Abstractions; namespace MockHttp; @@ -93,21 +92,19 @@ public void Given_that_actual_stream_is_null_when_creating_instance_it_should_th [Fact] public void Given_that_actual_stream_is_not_readable_when_creating_instance_it_should_throw() { - var actualStream = new Mock(); - actualStream - .Setup(m => m.CanRead) - .Returns(false) - .Verifiable(); + using Stream actualStream = Substitute.For(); + actualStream.CanRead.Returns(false); // Act - Func act = () => new RateLimitedStream(actualStream.Object, 1024); + // ReSharper disable once AccessToDisposedClosure + Func act = () => new RateLimitedStream(actualStream, 1024); // Assert act.Should() .ThrowExactly() .WithMessage("Cannot read from stream.*") .WithParameterName(nameof(actualStream)); - actualStream.Verify(); + _ = actualStream.Received(1).CanRead; } [Theory] @@ -169,31 +166,28 @@ public void When_setting_length_it_should_throw() [Fact] public void When_flushing_it_should_flush_underlying() { - var streamMock = new Mock { CallBase = true }; - using MemoryStream? stream = streamMock.Object; - var sut = new RateLimitedStream(stream, 1024); + using MemoryStream streamStub = Substitute.ForPartsOf(); + var sut = new RateLimitedStream(streamStub, 1024); // Act sut.Flush(); // Assert - streamMock.Verify(m => m.Flush(), Times.Once); + streamStub.Received(1).Flush(); } [Fact] public void When_disposing_it_should_dispose_underlying() { - var streamMock = new Mock { CallBase = true }; - using MemoryStream? stream = streamMock.Object; - var sut = new RateLimitedStream(stream, 1024); + using MemoryStream streamStub = Substitute.For(); + streamStub.CanRead.Returns(true); + var sut = new RateLimitedStream(streamStub, 1024); // Act sut.Dispose(); // Assert - streamMock - .Protected() - .Verify("Dispose", Times.Once(), true, true); + streamStub.Received(1).Close(); } public void Dispose() diff --git a/test/MockHttp.Tests/RequestMatchingTests.cs b/test/MockHttp.Tests/RequestMatchingTests.cs index 628202f0..16150d44 100644 --- a/test/MockHttp.Tests/RequestMatchingTests.cs +++ b/test/MockHttp.Tests/RequestMatchingTests.cs @@ -12,19 +12,18 @@ public class RequestMatchingTests public RequestMatchingTests() { - static HttpRequestMatcher CreateMatcherStub(Func returns) - { - var matcherMock = new Mock(); - matcherMock - .Setup(m => m.IsExclusive) - .Returns(returns); - return matcherMock.Object; - } - _sut = new RequestMatching(); _matcher1 = CreateMatcherStub(() => _isExclusive1); _matcher2 = CreateMatcherStub(() => _isExclusive2); + return; + + static HttpRequestMatcher CreateMatcherStub(Func returns) + { + HttpRequestMatcher matcherMock = Substitute.For(); + matcherMock.IsExclusive.Returns(_ => returns()); + return matcherMock; + } } [Fact] diff --git a/test/MockHttp.Tests/Responses/EmptyContentTests.cs b/test/MockHttp.Tests/Responses/EmptyContentTests.cs index 430d3e23..0da426f2 100644 --- a/test/MockHttp.Tests/Responses/EmptyContentTests.cs +++ b/test/MockHttp.Tests/Responses/EmptyContentTests.cs @@ -4,12 +4,7 @@ namespace MockHttp.Responses; public class EmptyContentTests { - private readonly EmptyContent _sut; - - public EmptyContentTests() - { - _sut = new EmptyContent(); - } + private readonly EmptyContent _sut = new(); [Fact] public async Task When_reading_string_it_should_return_empty() @@ -70,7 +65,7 @@ public void When_copying_sync_it_should_not_throw() { using var ms = new MemoryStream(); // ReSharper disable once MethodHasAsyncOverload - _sut.CopyTo(ms, Mock.Of(), CancellationToken.None); + _sut.CopyTo(ms, Substitute.For(), CancellationToken.None); ms.Length.Should().Be(0); }; diff --git a/test/MockHttp.Tests/Responses/TimeoutBehaviorTests.cs b/test/MockHttp.Tests/Responses/TimeoutBehaviorTests.cs index 36f26d1f..00d05fe1 100644 --- a/test/MockHttp.Tests/Responses/TimeoutBehaviorTests.cs +++ b/test/MockHttp.Tests/Responses/TimeoutBehaviorTests.cs @@ -15,10 +15,10 @@ public async Task Given_that_timeout_is_not_zero_when_sending_it_should_timeout_ var timeout = TimeSpan.FromMilliseconds(timeoutInMilliseconds); var sut = new TimeoutBehavior(timeout); var sw = new Stopwatch(); - var next = new Mock(); + ResponseHandlerDelegate nextStub = Substitute.For(); // Act - Func act = () => sut.HandleAsync(new MockHttpRequestContext(new HttpRequestMessage()), new HttpResponseMessage(), next.Object, CancellationToken.None); + Func act = () => sut.HandleAsync(new MockHttpRequestContext(new HttpRequestMessage()), new HttpResponseMessage(), nextStub, CancellationToken.None); // Assert sw.Start(); @@ -30,7 +30,7 @@ public async Task Given_that_timeout_is_not_zero_when_sending_it_should_timeout_ #endif sw.Elapsed.Should().BeGreaterThanOrEqualTo(timeout); - next.VerifyNoOtherCalls(); + nextStub.ReceivedCalls().Should().BeEmpty(); } [Fact] @@ -39,16 +39,16 @@ public async Task Given_that_cancellation_token_is_cancelled_when_sending_it_sho var timeout = TimeSpan.FromSeconds(60); var sut = new TimeoutBehavior(timeout); var ct = new CancellationToken(true); - var next = new Mock(); + ResponseHandlerDelegate nextStub = Substitute.For(); // Act var sw = Stopwatch.StartNew(); - Func act = () => sut.HandleAsync(new MockHttpRequestContext(new HttpRequestMessage()), new HttpResponseMessage(), next.Object, ct); + Func act = () => sut.HandleAsync(new MockHttpRequestContext(new HttpRequestMessage()), new HttpResponseMessage(), nextStub, ct); // Assert await act.Should().ThrowAsync().Where(ex => ex.InnerException == null); sw.Elapsed.Should().BeLessThan(timeout); - next.VerifyNoOtherCalls(); + nextStub.ReceivedCalls().Should().BeEmpty(); } [Theory]