From a4cf071def7b91448c6cf9e2aa7a1c8e0a121459 Mon Sep 17 00:00:00 2001 From: Chris Hannon Date: Sun, 28 Jan 2024 19:53:48 -0700 Subject: [PATCH] Replaced Moq with NSubstitute --- GrowthBook.Tests/ApiTests/ApiUnitTest.cs | 8 +-- .../ApiTests/FeatureRefreshWorkerTests.cs | 21 +++--- .../ApiTests/FeatureRepositoryTests.cs | 70 +++++++------------ GrowthBook.Tests/GrowthBook.Tests.csproj | 2 +- GrowthBook.Tests/UnitTest.cs | 3 - 5 files changed, 41 insertions(+), 63 deletions(-) diff --git a/GrowthBook.Tests/ApiTests/ApiUnitTest.cs b/GrowthBook.Tests/ApiTests/ApiUnitTest.cs index fb20eca..baac222 100644 --- a/GrowthBook.Tests/ApiTests/ApiUnitTest.cs +++ b/GrowthBook.Tests/ApiTests/ApiUnitTest.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using GrowthBook.Api; using Microsoft.Extensions.Logging; -using Moq; +using NSubstitute; namespace GrowthBook.Tests.ApiTests; @@ -16,15 +16,15 @@ public abstract class ApiUnitTest : UnitTest protected const string SecondFeatureId = nameof(SecondFeatureId); protected readonly ILogger _logger; - protected readonly Mock _cache; + protected readonly IGrowthBookFeatureCache _cache; protected readonly Feature _firstFeature; protected readonly Feature _secondFeature; protected readonly Dictionary _availableFeatures; public ApiUnitTest() { - _logger = Mock.Of>(); - _cache = StrictMockOf(); + _logger = Substitute.For>(); + _cache = Substitute.For(); _firstFeature = new() { DefaultValue = 1 }; _secondFeature = new() { DefaultValue = 2 }; diff --git a/GrowthBook.Tests/ApiTests/FeatureRefreshWorkerTests.cs b/GrowthBook.Tests/ApiTests/FeatureRefreshWorkerTests.cs index 8f521f3..938708e 100644 --- a/GrowthBook.Tests/ApiTests/FeatureRefreshWorkerTests.cs +++ b/GrowthBook.Tests/ApiTests/FeatureRefreshWorkerTests.cs @@ -12,8 +12,8 @@ using FluentAssertions; using GrowthBook.Api; using Microsoft.Extensions.Logging; -using Moq; using Newtonsoft.Json; +using NSubstitute; using Xunit; namespace GrowthBook.Tests.ApiTests; @@ -112,7 +112,7 @@ public FeatureRefreshWorkerTests() _httpClientFactory = new TestHttpClientFactory(); _httpClientFactory.ResponseContent = _availableFeatures; _httpClientFactory.StreamResponseContent = _availableFeatures.Take(1).ToDictionary(x => x.Key, x => x.Value); - _worker = new(_logger, _httpClientFactory, _config, _cache.Object); + _worker = new(_logger, _httpClientFactory, _config, _cache); } [Fact] @@ -131,15 +131,14 @@ public async Task HttpRequestWithSuccessStatusThatPrefersApiCallWillGetFeaturesF _config.PreferServerSentEvents = false; _cache - .Setup(x => x.RefreshWith(It.IsAny>(), It.IsAny())) - .Returns(Task.CompletedTask) - .Verifiable(); + .RefreshWith(Arg.Any>(), Arg.Any()) + .Returns(Task.CompletedTask); var features = await _worker.RefreshCacheFromApi(); features.Should().BeEquivalentTo(_availableFeatures); - Mock.Verify(_cache); + await _cache.Received(1).RefreshWith(Arg.Any>(), Arg.Any()); } [Fact] @@ -157,13 +156,13 @@ public async Task HttpResponseWithServerSentEventSupportWillStartBackgroundListe var resetEvent = new AutoResetEvent(false); _cache - .Setup(x => x.RefreshWith(It.IsAny>(), It.IsAny())) - .Callback((IDictionary x, CancellationToken? _) => + .RefreshWith(Arg.Any>(), Arg.Any()) + .Returns(Task.CompletedTask) + .AndDoes(x => { - cachedResults.Enqueue(x); + cachedResults.Enqueue((IDictionary)x[0]); resetEvent.Set(); - }) - .Returns(Task.CompletedTask); + }); var features = await _worker.RefreshCacheFromApi(); diff --git a/GrowthBook.Tests/ApiTests/FeatureRepositoryTests.cs b/GrowthBook.Tests/ApiTests/FeatureRepositoryTests.cs index 4630c12..f4d5ad7 100644 --- a/GrowthBook.Tests/ApiTests/FeatureRepositoryTests.cs +++ b/GrowthBook.Tests/ApiTests/FeatureRepositoryTests.cs @@ -6,32 +6,28 @@ using System.Threading.Tasks; using GrowthBook.Api; using Microsoft.Extensions.Logging; -using Moq; +using NSubstitute; using Xunit; namespace GrowthBook.Tests.ApiTests; public class FeatureRepositoryTests : ApiUnitTest { - private readonly Mock _backgroundWorker; + private readonly IGrowthBookFeatureRefreshWorker _backgroundWorker; private readonly FeatureRepository _featureRepository; public FeatureRepositoryTests() { - _backgroundWorker = StrictMockOf(); - _featureRepository = new(_logger, _cache.Object, _backgroundWorker.Object); + _backgroundWorker = Substitute.For(); + _featureRepository = new(_logger, _cache, _backgroundWorker); } [Fact] public void CancellingRepositoryWillCancelBackgroundWorker() { - _backgroundWorker - .Setup(x => x.Cancel()) - .Verifiable(); - _featureRepository.Cancel(); - _backgroundWorker.Verify(x => x.Cancel(), Times.Once, "Cancelling the background worker did not succeed"); + _backgroundWorker.Received(1).Cancel(); } [Theory] @@ -39,15 +35,11 @@ public void CancellingRepositoryWillCancelBackgroundWorker() [InlineData(false, false)] public async Task GettingFeaturesWhenApiCallIsUnnecessaryWillGetFromCache(bool isCacheExpired, bool? isForcedRefresh) { - _cache - .SetupGet(x => x.IsCacheExpired) - .Returns(isCacheExpired) - .Verifiable(); + _cache.IsCacheExpired.Returns(isCacheExpired); _cache - .Setup(x => x.GetFeatures(It.IsAny())) - .ReturnsAsync(_availableFeatures) - .Verifiable(); + .GetFeatures(Arg.Any()) + .Returns(_availableFeatures); var options = isForcedRefresh switch { @@ -57,7 +49,7 @@ public async Task GettingFeaturesWhenApiCallIsUnnecessaryWillGetFromCache(bool i var features = await _featureRepository.GetFeatures(options); - Mock.Verify(_cache); + await _cache.Received(1).GetFeatures(Arg.Any()); } [Theory] @@ -67,25 +59,17 @@ public async Task GettingFeaturesWhenApiCallIsUnnecessaryWillGetFromCache(bool i [InlineData(true, true)] public async Task GettingFeaturesWhenApiCallIsRequiredWithoutWaitingForRetrievalWillGetFromCache(bool isCacheExpired, bool? isForcedRefresh) { - _cache - .SetupGet(x => x.IsCacheExpired) - .Returns(isCacheExpired) - .Verifiable(); + _cache.IsCacheExpired.Returns(isCacheExpired); - _cache - .SetupGet(x => x.FeatureCount) - .Returns(_availableFeatures.Count) - .Verifiable(); + _cache.FeatureCount.Returns(_availableFeatures.Count); _cache - .Setup(x => x.GetFeatures(It.IsAny())) - .ReturnsAsync(_availableFeatures) - .Verifiable(); + .GetFeatures(Arg.Any()) + .Returns(_availableFeatures); _backgroundWorker - .Setup(x => x.RefreshCacheFromApi(It.IsAny())) - .ReturnsAsync(_availableFeatures) - .Verifiable(); + .RefreshCacheFromApi(Arg.Any()) + .Returns(_availableFeatures); var options = isForcedRefresh switch { @@ -95,7 +79,10 @@ public async Task GettingFeaturesWhenApiCallIsRequiredWithoutWaitingForRetrieval var features = await _featureRepository.GetFeatures(options); - Mock.Verify(_cache, _backgroundWorker); + _ = _cache.Received(2).IsCacheExpired; + _ = _cache.Received(1).FeatureCount; + _ = _cache.Received(1).GetFeatures(Arg.Any()); + _ = _backgroundWorker.Received(1).RefreshCacheFromApi(Arg.Any()); } [Theory] @@ -105,20 +92,13 @@ public async Task GettingFeaturesWhenApiCallIsRequiredWithoutWaitingForRetrieval [InlineData(true, true)] public async Task GettingFeaturesWhenApiCallIsRequiredWithWaitingForRetrievalWillGetFromApiCallInsteadOfCache(bool isCacheEmpty, bool? isForcedWait) { - _cache - .SetupGet(x => x.IsCacheExpired) - .Returns(true) - .Verifiable(); + _cache.IsCacheExpired.Returns(true); - _cache - .SetupGet(x => x.FeatureCount) - .Returns(isCacheEmpty ? 0 : 1) - .Verifiable(); + _cache.FeatureCount.Returns(isCacheEmpty ? 0 : 1); _backgroundWorker - .Setup(x => x.RefreshCacheFromApi(It.IsAny())) - .ReturnsAsync(_availableFeatures) - .Verifiable(); + .RefreshCacheFromApi(Arg.Any()) + .Returns(_availableFeatures); var options = isForcedWait switch { @@ -128,6 +108,8 @@ public async Task GettingFeaturesWhenApiCallIsRequiredWithWaitingForRetrievalWil var features = await _featureRepository.GetFeatures(options); - Mock.Verify(_cache, _backgroundWorker); + _ = _cache.Received(2).IsCacheExpired; + _ = _cache.Received(2).FeatureCount; + _ = _backgroundWorker.Received(1).RefreshCacheFromApi(Arg.Any()); } } diff --git a/GrowthBook.Tests/GrowthBook.Tests.csproj b/GrowthBook.Tests/GrowthBook.Tests.csproj index 6969163..700d3be 100644 --- a/GrowthBook.Tests/GrowthBook.Tests.csproj +++ b/GrowthBook.Tests/GrowthBook.Tests.csproj @@ -31,7 +31,7 @@ - + all diff --git a/GrowthBook.Tests/UnitTest.cs b/GrowthBook.Tests/UnitTest.cs index 692da9d..178e9ef 100644 --- a/GrowthBook.Tests/UnitTest.cs +++ b/GrowthBook.Tests/UnitTest.cs @@ -6,7 +6,6 @@ using System.Text; using System.Threading.Tasks; using GrowthBook.Tests.Json; -using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; @@ -227,6 +226,4 @@ public static IEnumerable GetMappedTestsInCategory(Type categoryType) return instance; } - - protected Mock StrictMockOf() where T : class => new Mock(MockBehavior.Strict); }