Skip to content

Commit

Permalink
Move from Moq to NSubstitute (#41)
Browse files Browse the repository at this point in the history
* Initial port to NSubstitute.

Signed-off-by: Phillip Hoff <phillip@orst.edu>

* Resolve liveness test break.

Signed-off-by: Phillip Hoff <phillip@orst.edu>

* Remove redundant mock creation.

Signed-off-by: Phillip Hoff <phillip@orst.edu>

* Make up for lack of "strict" mode.

Signed-off-by: Phillip Hoff <phillip@orst.edu>

---------

Signed-off-by: Phillip Hoff <phillip@orst.edu>
  • Loading branch information
philliphoff authored Aug 22, 2023
1 parent 719c583 commit 2023d94
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 294 deletions.
63 changes: 31 additions & 32 deletions src/Dapr.PluggableComponents.Tests/Adaptors/AdaptorFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
using Dapr.PluggableComponents.Components.StateStore;
using Grpc.Core;
using Microsoft.Extensions.Logging;
using Moq;
using NSubstitute;

namespace Dapr.PluggableComponents.Adaptors;

Expand All @@ -26,11 +26,11 @@ internal sealed class AdaptorFixture<TAdaptor, TInterface> : AdaptorFixture, IDi
{
private readonly Lazy<TAdaptor> adaptor;

public AdaptorFixture(Func<ILogger<TAdaptor>, IDaprPluggableComponentProvider<TInterface>, TAdaptor> adaptorFactory, Mock<TInterface>? mockComponent = null)
public AdaptorFixture(Func<ILogger<TAdaptor>, IDaprPluggableComponentProvider<TInterface>, TAdaptor> adaptorFactory, TInterface? mockComponent = null)
{
this.MockComponent = mockComponent ?? new Mock<TInterface>();
this.MockComponent = mockComponent ?? Substitute.For<TInterface>();

this.adaptor = new Lazy<TAdaptor>(() => Create<TAdaptor, TInterface>(this.MockComponent.Object, adaptorFactory));
this.adaptor = new Lazy<TAdaptor>(() => Create<TAdaptor, TInterface>(this.MockComponent, adaptorFactory));
}

/// <remarks>
Expand All @@ -41,7 +41,7 @@ public AdaptorFixture(Func<ILogger<TAdaptor>, IDaprPluggableComponentProvider<TI

public TestServerCallContext Context { get; } = new TestServerCallContext();

public Mock<TInterface> MockComponent { get; }
public TInterface MockComponent { get; }

#region IDisposable Members

Expand All @@ -55,22 +55,22 @@ public void Dispose()

internal abstract class AdaptorFixture
{
public static AdaptorFixture<InputBindingAdaptor, IInputBinding> CreateInputBinding(Mock<IInputBinding>? mockComponent = null)
public static AdaptorFixture<InputBindingAdaptor, IInputBinding> CreateInputBinding(IInputBinding? mockComponent = null)
{
return new AdaptorFixture<InputBindingAdaptor, IInputBinding>((logger, componentProvider) => new InputBindingAdaptor(logger, componentProvider), mockComponent);
}

public static AdaptorFixture<OutputBindingAdaptor, IOutputBinding> CreateOutputBinding(Mock<IOutputBinding>? mockComponent = null)
public static AdaptorFixture<OutputBindingAdaptor, IOutputBinding> CreateOutputBinding(IOutputBinding? mockComponent = null)
{
return new AdaptorFixture<OutputBindingAdaptor, IOutputBinding>((logger, componentProvider) => new OutputBindingAdaptor(logger, componentProvider), mockComponent);
}

public static AdaptorFixture<PubSubAdaptor, IPubSub> CreatePubSub(Mock<IPubSub>? mockComponent = null)
public static AdaptorFixture<PubSubAdaptor, IPubSub> CreatePubSub(IPubSub? mockComponent = null)
{
return new AdaptorFixture<PubSubAdaptor, IPubSub>((logger, componentProvider) => new PubSubAdaptor(logger, componentProvider), mockComponent);
}

public static AdaptorFixture<StateStoreAdaptor, IStateStore> CreateStateStore(Mock<IStateStore>? mockComponent = null)
public static AdaptorFixture<StateStoreAdaptor, IStateStore> CreateStateStore(IStateStore? mockComponent = null)
{
return new AdaptorFixture<StateStoreAdaptor, IStateStore>((logger, componentProvider) => new StateStoreAdaptor(logger, componentProvider), mockComponent);
}
Expand All @@ -83,7 +83,8 @@ public static async Task TestInitAsync<TAdaptor, TInterface>(
using var fixture = adaptorFactory();

fixture.MockComponent
.Setup(component => component.InitAsync(It.IsAny<Components.MetadataRequest>(), It.IsAny<CancellationToken>()));
.InitAsync(Arg.Any<Components.MetadataRequest>(), Arg.Any<CancellationToken>())
.Returns(Task.CompletedTask);

var properties = new Dictionary<string, string>()
{
Expand All @@ -97,16 +98,15 @@ public static async Task TestInitAsync<TAdaptor, TInterface>(

await initCall(fixture, metadataRequest);

fixture.MockComponent
.Verify(
component => component.InitAsync(
It.Is<Components.MetadataRequest>(request => ConversionAssert.MetadataEqual(properties, request.Properties)),
It.Is<CancellationToken>(token => token == fixture.Context.CancellationToken)),
Times.Once());
await fixture.MockComponent
.Received(1)
.InitAsync(
Arg.Is<Components.MetadataRequest>(request => ConversionAssert.MetadataEqual(properties, request.Properties)),
Arg.Is<CancellationToken>(token => token == fixture.Context.CancellationToken));
}

public static async Task TestPingAsync<TAdaptor, TInterface>(
Func<Mock<TInterface>?, AdaptorFixture<TAdaptor, TInterface>> adaptorFactory,
Func<TInterface?, AdaptorFixture<TAdaptor, TInterface>> adaptorFactory,
Func<AdaptorFixture<TAdaptor, TInterface>, Task> pingCall)
where TInterface : class, IPluggableComponent
{
Expand All @@ -115,46 +115,45 @@ public static async Task TestPingAsync<TAdaptor, TInterface>(
}

private static async Task PingWithNoLiveness<TAdaptor, TInterface>(
Func<Mock<TInterface>?, AdaptorFixture<TAdaptor, TInterface>> adaptorFactory,
Func<TInterface?, AdaptorFixture<TAdaptor, TInterface>> adaptorFactory,
Func<AdaptorFixture<TAdaptor, TInterface>, Task> pingCall)
where TInterface : class, IPluggableComponent
{
using var fixture = adaptorFactory(new Mock<TInterface>(MockBehavior.Strict));
using var fixture = adaptorFactory(Substitute.For<TInterface>());

await pingCall(fixture);
}

private static async Task PingWithLiveness<TAdaptor, TInterface>(
Func<Mock<TInterface>?, AdaptorFixture<TAdaptor, TInterface>> adaptorFactory,
Func<TInterface?, AdaptorFixture<TAdaptor, TInterface>> adaptorFactory,
Func<AdaptorFixture<TAdaptor, TInterface>, Task> pingCall)
where TInterface : class, IPluggableComponent
{
using var fixture = adaptorFactory(null);
using var fixture = adaptorFactory(Substitute.For<TInterface, IPluggableComponentLiveness>());

var mockLiveness = fixture.MockComponent.As<IPluggableComponentLiveness>();
var mockLiveness = (IPluggableComponentLiveness)fixture.MockComponent;

mockLiveness
.Setup(component => component.PingAsync(It.IsAny<CancellationToken>()));
.PingAsync(Arg.Any<CancellationToken>())
.Returns(Task.CompletedTask);

await pingCall(fixture);

mockLiveness
.Verify(
component => component.PingAsync(
It.Is<CancellationToken>(token => token == fixture.Context.CancellationToken)),
Times.Once());
await mockLiveness
.Received(1)
.PingAsync(Arg.Is<CancellationToken>(token => token == fixture.Context.CancellationToken));
}

protected static TAdaptor Create<TAdaptor, TInterface>(TInterface component, Func<ILogger<TAdaptor>, IDaprPluggableComponentProvider<TInterface>, TAdaptor> adaptorFactory)
{
var logger = new Mock<ILogger<TAdaptor>>();
var logger = Substitute.For<ILogger<TAdaptor>>();

var mockComponentProvider = new Mock<IDaprPluggableComponentProvider<TInterface>>();
var mockComponentProvider = Substitute.For<IDaprPluggableComponentProvider<TInterface>>();

mockComponentProvider
.Setup(componentProvider => componentProvider.GetComponent(It.IsAny<ServerCallContext>()))
.GetComponent(Arg.Any<ServerCallContext>())
.Returns(component);

return adaptorFactory(logger.Object, mockComponentProvider.Object);
return adaptorFactory(logger, mockComponentProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
using Dapr.PluggableComponents.Components.Bindings;
using Dapr.Proto.Components.V1;
using Grpc.Core;
using Moq;
using NSubstitute;
using Xunit;

namespace Dapr.PluggableComponents.Adaptors;
Expand Down Expand Up @@ -49,27 +49,22 @@ public async Task ReadServerDone()
var reader = new AsyncStreamReader<ReadRequest>();

fixture.MockComponent
.Setup(component => component.ReadAsync(It.IsAny<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(), It.IsAny<CancellationToken>()))
.Returns<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>, CancellationToken>(
(deliveryHandler, cancellationToken) =>
{
return Task.CompletedTask;
});
.ReadAsync(Arg.Any<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(), Arg.Any<CancellationToken>())
.Returns(Task.CompletedTask);

var mockWriter = new Mock<IServerStreamWriter<ReadResponse>>();
var mockWriter = Substitute.For<IServerStreamWriter<ReadResponse>>();

await fixture.Adaptor.Read(
reader,
mockWriter.Object,
mockWriter,
fixture.Context);

fixture.MockComponent
.Verify(
component => component.ReadAsync(
It.IsAny<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(),
await fixture.MockComponent
.Received(1)
.ReadAsync(
Arg.Any<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(),
// NOTE: The adaptor provides its own cancellation token.
It.IsAny<CancellationToken>()),
Times.Once());
Arg.Any<CancellationToken>());
}

[Fact(Timeout = TimeoutInMs)]
Expand All @@ -80,31 +75,30 @@ public async Task ReadClientHasNoMoreMessages()
var reader = new AsyncStreamReader<ReadRequest>();

fixture.MockComponent
.Setup(component => component.ReadAsync(It.IsAny<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(), It.IsAny<CancellationToken>()))
.Returns<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>, CancellationToken>(
(deliveryHandler, cancellationToken) =>
.ReadAsync(Arg.Any<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(), Arg.Any<CancellationToken>())
.Returns(
callInfo =>
{
return Task.Delay(-1, cancellationToken);
return Task.Delay(-1, (CancellationToken)callInfo[1]);
});

var mockWriter = new Mock<IServerStreamWriter<ReadResponse>>();
var mockWriter = Substitute.For<IServerStreamWriter<ReadResponse>>();

var readTask = fixture.Adaptor.Read(
reader,
mockWriter.Object,
mockWriter,
fixture.Context);

reader.Complete();

await Assert.ThrowsAnyAsync<OperationCanceledException>(() => readTask);

fixture.MockComponent
.Verify(
component => component.ReadAsync(
It.IsAny<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(),
// NOTE: The adaptor provides its own cancellation token.
It.IsAny<CancellationToken>()),
Times.Once());
await fixture.MockComponent
.Received(1)
.ReadAsync(
Arg.Any<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(),
// NOTE: The adaptor provides its own cancellation token.
Arg.Any<CancellationToken>());
}

[Fact(Timeout = TimeoutInMs)]
Expand All @@ -115,31 +109,30 @@ public async Task ReadClientCanceled()
var reader = new AsyncStreamReader<ReadRequest>();

fixture.MockComponent
.Setup(component => component.ReadAsync(It.IsAny<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(), It.IsAny<CancellationToken>()))
.Returns<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>, CancellationToken>(
(deliveryHandler, cancellationToken) =>
.ReadAsync(Arg.Any<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(), Arg.Any<CancellationToken>())
.Returns(
callInfo =>
{
return Task.Delay(-1, cancellationToken);
return Task.Delay(-1, (CancellationToken)callInfo[1]);
});

var mockWriter = new Mock<IServerStreamWriter<ReadResponse>>();
var mockWriter = Substitute.For<IServerStreamWriter<ReadResponse>>();

var readTask = fixture.Adaptor.Read(
reader,
mockWriter.Object,
mockWriter,
fixture.Context);

fixture.Context.Cancel();

await Assert.ThrowsAnyAsync<OperationCanceledException>(() => readTask);

fixture.MockComponent
.Verify(
component => component.ReadAsync(
It.IsAny<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(),
// NOTE: The adaptor provides its own cancellation token.
It.IsAny<CancellationToken>()),
Times.Once());
await fixture.MockComponent
.Received(1)
.ReadAsync(
Arg.Any<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(),
// NOTE: The adaptor provides its own cancellation token.
Arg.Any<CancellationToken>());
}

[Fact(Timeout = TimeoutInMs)]
Expand All @@ -153,10 +146,12 @@ public async Task ReadWithMessage()
var reader = new AsyncStreamReader<ReadRequest>();

fixture.MockComponent
.Setup(component => component.ReadAsync(It.IsAny<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(), It.IsAny<CancellationToken>()))
.Returns<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>, CancellationToken>(
async (deliveryHandler, cancellationToken) =>
.ReadAsync(Arg.Any<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(), Arg.Any<CancellationToken>())
.Returns(
async callInfo =>
{
var deliveryHandler = (MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>)callInfo[0];
await deliveryHandler(
new InputBindingReadResponse { ContentType = contentType },
response =>
Expand All @@ -169,31 +164,32 @@ await deliveryHandler(
});
});

var mockWriter = new Mock<IServerStreamWriter<ReadResponse>>();
var mockWriter = Substitute.For<IServerStreamWriter<ReadResponse>>();

mockWriter
.Setup(writer => writer.WriteAsync(It.IsAny<ReadResponse>()))
.Returns<ReadResponse>(
async response =>
.WriteAsync(Arg.Any<ReadResponse>())
.Returns(
async callInfo =>
{
var response = (ReadResponse)callInfo[0];
Assert.Equal(contentType, response.ContentType);
await reader.AddAsync(new ReadRequest { ResponseError = new AckResponseError { Message = error }, MessageId = response.MessageId });
});

var pullMessagesTask = fixture.Adaptor.Read(
reader,
mockWriter.Object,
mockWriter,
fixture.Context);

await pullMessagesTask;

fixture.MockComponent
.Verify(
component => component.ReadAsync(
It.IsAny<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(),
// NOTE: The adaptor provides its own cancellation token.
It.IsAny<CancellationToken>()),
Times.Once());
await fixture.MockComponent
.Received(1)
.ReadAsync(
Arg.Any<MessageDeliveryHandler<InputBindingReadRequest, InputBindingReadResponse>>(),
// NOTE: The adaptor provides its own cancellation token.
Arg.Any<CancellationToken>());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
using Dapr.Client.Autogen.Grpc.v1;
using Dapr.PluggableComponents.Components.Bindings;
using Dapr.Proto.Components.V1;
using Moq;
using NSubstitute;
using Xunit;

namespace Dapr.PluggableComponents.Adaptors;
Expand Down Expand Up @@ -48,15 +48,16 @@ public async Task InvokeTests()
var request = new InvokeRequest { Operation = operation };

fixture.MockComponent
.Setup(component => component.InvokeAsync(It.Is<OutputBindingInvokeRequest>(request => request.Operation == operation), It.Is<CancellationToken>(token => token == fixture.Context.CancellationToken)))
.ReturnsAsync(new OutputBindingInvokeResponse { ContentType = contentType });
.InvokeAsync(Arg.Is<OutputBindingInvokeRequest>(request => request.Operation == operation), Arg.Is<CancellationToken>(token => token == fixture.Context.CancellationToken))
.Returns(new OutputBindingInvokeResponse { ContentType = contentType });

var response = await fixture.Adaptor.Invoke(new InvokeRequest { Operation = operation }, fixture.Context);

Assert.Equal(contentType, response.ContentType);

fixture.MockComponent
.Verify(component => component.InvokeAsync(It.Is<OutputBindingInvokeRequest>(request => request.Operation == operation), It.Is<CancellationToken>(token => token == fixture.Context.CancellationToken)), Times.Once);
await fixture.MockComponent
.Received(1)
.InvokeAsync(Arg.Is<OutputBindingInvokeRequest>(request => request.Operation == operation), Arg.Is<CancellationToken>(token => token == fixture.Context.CancellationToken));
}

[Fact]
Expand All @@ -67,14 +68,15 @@ public async Task ListOperationsTests()
var operations = new[] { "operation1", "operation2" };

fixture.MockComponent
.Setup(component => component.ListOperationsAsync(It.Is<CancellationToken>(token => token == fixture.Context.CancellationToken)))
.ReturnsAsync(operations);
.ListOperationsAsync(Arg.Is<CancellationToken>(token => token == fixture.Context.CancellationToken))
.Returns(operations);

var response = await fixture.Adaptor.ListOperations(new ListOperationsRequest(), fixture.Context);

Assert.Equal(operations, response.Operations);

fixture.MockComponent
.Verify(component => component.ListOperationsAsync(It.Is<CancellationToken>(token => token == fixture.Context.CancellationToken)), Times.Once);
await fixture.MockComponent
.Received(1)
.ListOperationsAsync(Arg.Is<CancellationToken>(token => token == fixture.Context.CancellationToken));
}
}
Loading

0 comments on commit 2023d94

Please sign in to comment.