FluentArrange lets you write clean Arrange blocks in your unit tests even when the constructor of the class under test has a lot of (mocked) dependencies.
Package | Version | Description |
---|---|---|
FluentArrange |
Core package | |
FluentArrange.NSubstitute |
When you use NSubstitute for mocking |
Consider the following example where we use NSubstitute to mock dependencies:
var sut = new ResetPasswordController(
Substitute.For<IAccountService>(),
Substitute.For<IAuditService>(),
Substitute.For<IMailService>());
With FluentArrange, an instance T
is instantiated using Arrange.Sut<T>
, and all of its constructor dependencies are auto-mocked:
var sut = Arrange.Sut<ResetPasswordController>();
Adding new dependencies will not break existing unit tests.
Most of the time, we need to arrange some behavior for our mocked dependencies:
var accountService = Substitute.For<IAccountService>();
accountService.FindEmail("[email protected]").Returns(new Account("foo"));
var mailService = Substitute.For<IMailService>();
mailService.SendMail("[email protected]").Returns(true);
var sut = new ResetPasswordController(
accountService,
Substitute.For<IAuditService>(),
mailService);
With FluentArrange, you can use the Fluent API WithDependency<T>
to achieve the exact same result as the code above:
var sut = Arrange.For<ResetPasswordController>()
.WithDependency<IAccountService>(x => x.FindEmail("[email protected]").Returns(new Account("foo")))
.WithDependency<IMailService>(x => x.SendMail("[email protected]").Returns(true));
As you might have noticed, we did not need to write arrange code for IAuditService
, as that will be automatically created for you.
The only time you need to call WithDependency<T>
is when you need to arrange the behavior of type T
.
Sometimes, you may want to use a fake implementation rather than letting FluentArrange automatically create mocked instances.
In that case, you can provide an instance using WithDependency<T>(T)
:
var context = Arrange.For<ResetPasswordController>()
.WithDependency<IAccountService>(new InMemoryAccountService());
or WithDependency<T>(T, Action<T>)
if you need to do more arranging:
var context = Arrange.For<ResetPasswordController>()
.WithDependency<IAccountService>(new InMemoryAccountService(), d =>
{
d.AddAccount("foo", "[email protected]");
d.AddAccount("foobar", "[email protected]");
});
or WithDependency<T, T2>(T2, Action<T2>)
if you need to call T2
-specific methods:
var context = Arrange.For<ResetPasswordController>()
.WithDependency<IAccountService, InMemoryAccountService>(new InMemoryAccountService(), d =>
{
d.AddTestAccounts();
});
Suppose you need to assert that a dependency's method has been called.
Well, you simply arrange code with Arrange.For<T>
instead of Arrange.Sut<T>
to get a FluentArrangeContext
object:
var context = Arrange.For<ResetPasswordController>();
To get the SUT, call the Sut
property of the context:
var sut = context.Sut;
To get the Dependency, simply call Dependency<T>
:
context.Dependency<IAccountService>();
or Dependency<T, T2>
to get access to T2
and its specific methods:
context.Dependency<IAccountService, AccountService>();
When used together, a unit test could look like this:
// Arrange
var context = Arrange.For<ResetPasswordController>();
// Act
context.Sut.Reset("[email protected]");
// Assert
context.Dependency<IAccountService>().Received(1).FindEmail("[email protected]");