Skip to content

Commit

Permalink
refactor: Move from moq to NSubstitute
Browse files Browse the repository at this point in the history
  • Loading branch information
linkdotnet committed Aug 11, 2023
1 parent 03f0059 commit 28f09e7
Show file tree
Hide file tree
Showing 14 changed files with 75 additions and 92 deletions.
4 changes: 2 additions & 2 deletions tests/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<PackageReference Include="AutoFixture" Version="4.18.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.18.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="NSubstitute" Version="5.0.0" />
<PackageReference Include="Shouldly" Version="4.2.1" />
<PackageReference Include="xunit" Version="2.5.0" />
<PackageReference Include="Xunit.Combinatorial" Version="1.5.25" />
Expand All @@ -39,7 +39,7 @@
<Using Include="Microsoft.AspNetCore.Components.Web" />
<Using Include="Microsoft.AspNetCore.Components.Routing" />
<Using Include="System.Reflection" />
<Using Include="Moq" />
<Using Include="NSubstitute" />
<Using Include="Shouldly" />
<Using Include="Xunit" />
<Using Include="Xunit.Abstractions" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void Test003()
[Fact(DisplayName = "Component is replaced in render tree with component from factory when matches returns true")]
public void Test010()
{
var mockComponent = Mock.Of<Simple1>();
var mockComponent = Substitute.For<Simple1>();
ComponentFactories.Add(type => type == typeof(Simple1), _ => mockComponent);

var cut = RenderComponent<Wrapper>(ps => ps.AddChildContent<Simple1>());
Expand All @@ -34,17 +34,21 @@ public void Test010()
[Fact(DisplayName = "Component is replaced in render tree with component from factory when matches returns true")]
public void Test011()
{
var mockRepo = new MockRepository(MockBehavior.Loose);
var mockSimple1 = Substitute.For<Simple1>();
var mockNoArgs = Substitute.For<NoArgs>();
ComponentFactories.Add(
type => type != typeof(TwoComponentWrapper),
mockRepo.CreateComponent);
type => CreateComponent(type, mockSimple1, mockNoArgs)
);

// Act
var cut = RenderComponent<TwoComponentWrapper>(ps => ps
.Add<Simple1>(p => p.First)
.Add<NoArgs>(p => p.Second));
.Add<Simple1>(p => p.First)
.Add<NoArgs>(p => p.Second));

cut.FindComponents<Simple1>().ShouldAllBe(x => Mock.Get(x.Instance));
cut.FindComponents<NoArgs>().ShouldAllBe(x => Mock.Get(x.Instance));
// Assert
cut.FindComponents<Simple1>().ShouldAllBe(x => x.Instance == mockSimple1);
cut.FindComponents<NoArgs>().ShouldAllBe(x => x.Instance == mockNoArgs);
}

[Fact(DisplayName = "When matches returns false, factory is never called")]
Expand All @@ -54,19 +58,12 @@ public void Test012()

Should.NotThrow(() => RenderComponent<Wrapper>(ps => ps.AddChildContent<Simple1>()));
}
}

internal static class MockRepositoryExtensions
{
private static readonly MethodInfo CreateMethodInfo = typeof(MockRepository)
.GetMethod(nameof(MockRepository.Create), Array.Empty<Type>());

public static IComponent CreateComponent(this MockRepository repository, Type type)

private static IComponent CreateComponent(Type type, Simple1 simple1, NoArgs noArgs)
{
var genericCreateMethod = CreateMethodInfo.MakeGenericMethod(type);
var mock = (Mock)genericCreateMethod.Invoke(repository, null);
return (IComponent)mock.Object;
if (type == typeof(Simple1)) return simple1;
if (type == typeof(NoArgs)) return noArgs;
throw new NotImplementedException($"No mock implementation provided for type {type.FullName}");
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ public void Test002()
[Fact(DisplayName = "Factory replaces one TComponent with instance in the render tree")]
public void Test010()
{
var simple1Mock = new Mock<Simple1>();
ComponentFactories.Add<Simple1>(simple1Mock.Object);
var simple1Mock = Substitute.For<Simple1>();
ComponentFactories.Add(simple1Mock);

var cut = RenderComponent<Wrapper>(ps => ps.AddChildContent<Simple1>());

cut.FindComponent<Simple1>()
.Instance.ShouldBeSameAs(simple1Mock.Object);
.Instance.ShouldBeSameAs(simple1Mock);
}

[Fact(DisplayName = "Factory throws if component instance is requested twice for TComponent that inherits from ComponentBase")]
public void Test020()
{
var simple1Mock = new Mock<Simple1>();
ComponentFactories.Add<Simple1>(simple1Mock.Object);
var simple1Mock = Substitute.For<Simple1>();
ComponentFactories.Add(simple1Mock);

Should.Throw<InvalidOperationException>(() => RenderComponent<TwoComponentWrapper>(ps => ps
.Add<Simple1>(p => p.First)
Expand All @@ -38,8 +38,8 @@ public void Test020()
[Fact(DisplayName = "Factory throws if component instance is requested twice for TComponent that implements from IComponent")]
public void Test021()
{
var simple1Mock = new Mock<BasicComponent>();
ComponentFactories.Add<BasicComponent>(simple1Mock.Object);
var simple1Mock = Substitute.For<BasicComponent>();
ComponentFactories.Add(simple1Mock);

Should.Throw<InvalidOperationException>(() => RenderComponent<TwoComponentWrapper>(ps => ps
.Add<BasicComponent>(p => p.First)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void Test002()
[Fact(DisplayName = "TComponent replaced in render tree with component from factory method")]
public void Test010()
{
var simple1Mock = Mock.Of<Simple1>();
var simple1Mock = Substitute.For<Simple1>();
ComponentFactories.Add<Simple1>(() => simple1Mock);

var cut = RenderComponent<Wrapper>(ps => ps.AddChildContent<Simple1>());
Expand All @@ -28,17 +28,17 @@ public void Test010()
[Fact(DisplayName = "Multiple TComponent replaced in render tree with component from factory method")]
public void Test011()
{
var mockRepo = new MockRepository(MockBehavior.Loose);
ComponentFactories.Add<Simple1>(() => mockRepo.Create<Simple1>().Object);
ComponentFactories.Add(() => Substitute.For<Simple1>());

var cut = RenderComponent<TwoComponentWrapper>(ps => ps
.Add<Simple1>(p => p.First)
.Add<Simple1>(p => p.Second));

cut.FindComponents<Simple1>()
.ShouldAllBe(
x => Mock.Get(x.Instance),
x => Mock.Get(x.Instance));
foreach (var component in cut.FindComponents<Simple1>())
{
Action checkIfSubstitute = () => component.Instance.Received();
checkIfSubstitute.ShouldNotThrow();
}
}
}

Expand Down
9 changes: 4 additions & 5 deletions tests/bunit.core.tests/Rendering/TestRendererTest.net5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Bunit.TestAssets.SampleComponents;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;

namespace Bunit.Rendering;
Expand All @@ -19,17 +18,17 @@ public partial class TestRendererTest : TestContext
"then it used to create components")]
public void Test1000()
{
var activatorMock = new Mock<IComponentActivator>();
activatorMock.Setup(x => x.CreateInstance(typeof(Wrapper))).Returns(new Wrapper());
var activatorMock = Substitute.For<IComponentActivator>();
activatorMock.CreateInstance(typeof(Wrapper)).Returns(new Wrapper());
using var renderer = new TestRenderer(
Services.GetService<IRenderedComponentActivator>(),
Services,
NullLoggerFactory.Instance,
activatorMock.Object);
activatorMock);

renderer.RenderComponent<Wrapper>(new ComponentParameterCollection());

activatorMock.Verify(x => x.CreateInstance(typeof(Wrapper)), Times.Once());
activatorMock.Received(1).CreateInstance(typeof(Wrapper));
}
}
#endif
32 changes: 16 additions & 16 deletions tests/bunit.core.tests/TestContextBaseTest.net5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,40 @@ public partial class TestContextBaseTest : TestContext
public void Test0001()
{
var mock = CreateMockComponentFactory(canCreate: _ => false, create: _ => null);
ComponentFactories.Add(mock.Object);
ComponentFactories.Add(mock);

RenderComponent<Simple1>();

mock.Verify(x => x.CanCreate(typeof(Simple1)), Times.Once);
mock.Verify(x => x.Create(It.IsAny<Type>()), Times.Never);
mock.Received(1).CanCreate(typeof(Simple1));
mock.DidNotReceive().Create(Arg.Any<Type>());
}

[Fact(DisplayName = "ComponentFactories Create() method is called when their CanCreate() method returns true")]
public void Test0002()
{
var mock = CreateMockComponentFactory(canCreate: _ => true, create: _ => new Simple1());
ComponentFactories.Add(mock.Object);
ComponentFactories.Add(mock);

RenderComponent<Simple1>();

mock.Verify(x => x.CanCreate(typeof(Simple1)), Times.Once);
mock.Verify(x => x.Create(typeof(Simple1)), Times.Once);
mock.Received(1).CanCreate(typeof(Simple1));
mock.Received(1).Create(typeof(Simple1));
}

[Fact(DisplayName = "ComponentFactories is used in last added order")]
public void Test0003()
{
var firstMock = CreateMockComponentFactory(canCreate: _ => true, create: _ => new Simple1());
var secondMock = CreateMockComponentFactory(canCreate: _ => true, create: _ => new Simple1());
ComponentFactories.Add(firstMock.Object);
ComponentFactories.Add(secondMock.Object);
ComponentFactories.Add(firstMock);
ComponentFactories.Add(secondMock);

RenderComponent<Simple1>();

firstMock.Verify(x => x.CanCreate(It.IsAny<Type>()), Times.Never);
firstMock.Verify(x => x.Create(It.IsAny<Type>()), Times.Never);
secondMock.Verify(x => x.CanCreate(typeof(Simple1)), Times.Once);
secondMock.Verify(x => x.Create(typeof(Simple1)), Times.Once);
firstMock.DidNotReceive().CanCreate(Arg.Any<Type>());
firstMock.DidNotReceive().Create(Arg.Any<Type>());
secondMock.Received(1).CanCreate(typeof(Simple1));
secondMock.Received(1).Create(typeof(Simple1));
}

[Fact(DisplayName = "DisposeComponents captures exceptions from DisposeAsync in Renderer.UnhandledException")]
Expand Down Expand Up @@ -160,11 +160,11 @@ public async ValueTask DisposeAsync()
}
}

private static Mock<IComponentFactory> CreateMockComponentFactory(Func<Type, bool> canCreate, Func<Type, IComponent> create)
private static IComponentFactory CreateMockComponentFactory(Func<Type, bool> canCreate, Func<Type, IComponent> create)
{
var result = new Mock<IComponentFactory>(MockBehavior.Strict);
result.Setup(x => x.CanCreate(It.IsAny<Type>())).Returns(canCreate);
result.Setup(x => x.Create(It.IsAny<Type>())).Returns(create);
var result = Substitute.For<IComponentFactory>();
result.CanCreate(Arg.Any<Type>()).Returns(call => canCreate((Type)call[0]));
result.Create(Arg.Any<Type>()).Returns(call => create((Type)call[0]));
return result;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public class RazorTestDiscovererTest
public RazorTestDiscovererTest()
{
options = TestFrameworkOptions.ForDiscovery();
messageBus = Mock.Of<IMessageSink>();
attribute = Mock.Of<IReflectionAttributeInfo>();
messageBus = Substitute.For<IMessageSink>();
attribute = Substitute.For<IReflectionAttributeInfo>();
}

[Fact(DisplayName = "Can find single razor test in test component")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Bunit.RazorTesting;
public sealed class RazorTestSourceInformationProviderTest : IDisposable
{
private readonly TestComponentRenderer renderer = new();
private readonly IMessageSink messageBus = Mock.Of<IMessageSink>();
private readonly IMessageSink messageBus = Substitute.For<IMessageSink>();

private RazorTestBase GetTest(Type testComponent, int testIndex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public static IEnumerable<object[]> GetCompareToMethods()
object p1 = p1Info.ParameterType.ToMockInstance();
object p2 = p2Info.ParameterType.ToMockInstance();

yield return new object[] { method, p1Info.Name!, new object[] { null!, p2! } };
yield return new object[] { method, p2Info.Name!, new object[] { p1!, null! } };
yield return new object[] { method, p1Info.Name!, new[] { null!, p2! } };
yield return new object[] { method, p2Info.Name!, new[] { p1!, null! } };
}
}

Expand Down
6 changes: 3 additions & 3 deletions tests/bunit.web.tests/Asserting/DiffAssertExtensionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public void Test002(IReadOnlyList<IDiff> diffs)
[Fact(DisplayName = "ShouldHaveSingleChange returns the single diff in input when there is only one")]
public void Test003()
{
var input = new IDiff[] { Mock.Of<IDiff>() };
var input = new[] { Substitute.For<IDiff>() };

var output = input.ShouldHaveSingleChange();

Expand All @@ -58,8 +58,8 @@ public static IEnumerable<object[]> GetDiffLists()
{
new IDiff[]
{
Mock.Of<IDiff>(),
Mock.Of<IDiff>(),
Substitute.For<IDiff>(),
Substitute.For<IDiff>(),
},
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public void Test200()
public void Test201()
{
var obj = new object();
Should.Throw<ActualExpectedAssertException>(() => obj.ShouldBeElementReferenceTo(Mock.Of<IElement>()));
Should.Throw<ActualExpectedAssertException>(() => obj.ShouldBeElementReferenceTo(Substitute.For<IElement>()));
}

[Fact(DisplayName = "ShouldBeElementReferenceTo throws if element reference does not point to the provided element")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,16 @@ public void Test002()
[Fact(DisplayName = "TriggerEventAsync throws if element was not rendered through blazor (has a TestRendere in its context)")]
public void Test003()
{
var elmMock = new Mock<IElement>();
var docMock = new Mock<IDocument>();
var ctxMock = new Mock<IBrowsingContext>();
var elmMock = Substitute.For<IElement>();
var docMock = Substitute.For<IDocument>();
var ctxMock = Substitute.For<IBrowsingContext>();

elmMock.Setup(x => x.GetAttribute(It.IsAny<string>())).Returns("1");
elmMock.SetupGet(x => x.Owner).Returns(docMock.Object);
docMock.SetupGet(x => x.Context).Returns(ctxMock.Object);
ctxMock.Setup(x => x.GetService<ITestRenderer>()).Returns(() => null!);
elmMock.GetAttribute(Arg.Any<string>()).Returns("1");
elmMock.Owner.Returns(docMock);
docMock.Context.Returns(ctxMock);
ctxMock.GetService<ITestRenderer>().Returns(x => null!);

Should.Throw<InvalidOperationException>(() => elmMock.Object.TriggerEventAsync("click", EventArgs.Empty));
Should.Throw<InvalidOperationException>(() => elmMock.TriggerEventAsync("click", EventArgs.Empty));
}

[Fact(DisplayName = "When clicking on an element with an event handler, " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public void ShouldSignOut(string randomUserName)
[Fact]
public void ShouldReturnSignOutStateOnValidateSignOutState()
{
var cut = new FakeSignOutSessionStateManager(Mock.Of<IJSRuntime>());
var cut = new FakeSignOutSessionStateManager(Substitute.For<IJSRuntime>());
cut.SetSignOutState();

var wasValidate = cut.ValidateSignOutState().Result;
Expand Down
23 changes: 5 additions & 18 deletions tests/bunit.web.tests/TestUtilities/MockingHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ namespace Bunit.TestUtilities;
/// </summary>
public static class MockingHelpers
{
private static readonly MethodInfo MockOfInfo = typeof(Mock)
.GetMethods()
.First(x => string.Equals(x.Name, nameof(Mock.Of), StringComparison.Ordinal) && x.GetParameters().Length == 0);

private static readonly Type DelegateType = typeof(MulticastDelegate);
private static readonly Type StringType = typeof(string);

Expand All @@ -24,15 +20,11 @@ public static object ToMockInstance(this Type type)

if (type.IsMockable())
{
var result = MockOfInfo.MakeGenericMethod(type).Invoke(null, Array.Empty<object>());

if (result is null)
throw new NotSupportedException($"Cannot create an mock of {type.FullName}.");

return result;
var result = Substitute.For(new[] { type }, Array.Empty<object>());
return result ?? throw new NotSupportedException($"Cannot create an mock of {type.FullName}.");
}

if (type.Equals(StringType))
if (type == StringType)
{
return string.Empty;
}
Expand All @@ -48,16 +40,11 @@ public static bool IsMockable(this Type type)
if (type is null)
throw new ArgumentNullException(nameof(type));

if (type.IsSealed)
return type.IsDelegateType();
return true;
return !type.IsSealed || type.IsDelegateType();
}

/// <summary>
/// Gets whether a type is a delegate type.
/// </summary>
public static bool IsDelegateType(this Type type)
{
return Equals(type, DelegateType);
}
public static bool IsDelegateType(this Type type) => type == DelegateType;
}

0 comments on commit 28f09e7

Please sign in to comment.