diff --git a/test/Common/AutoFixture/SutProvider.cs b/test/Common/AutoFixture/SutProvider.cs
index fefe6c3ebf..f90e7be445 100644
--- a/test/Common/AutoFixture/SutProvider.cs
+++ b/test/Common/AutoFixture/SutProvider.cs
@@ -4,8 +4,19 @@ using AutoFixture.Kernel;
namespace Bit.Test.Common.AutoFixture;
+///
+/// A utility class that encapsulates a system under test (sut) and its dependencies.
+/// By default, all dependencies are initialized as mocks using the NSubstitute library.
+/// SutProvider provides an interface for accessing these dependencies in the arrange and assert stages of your tests.
+///
+/// The concrete implementation of the class being tested.
public class SutProvider : ISutProvider
{
+ ///
+ /// A record of the configured dependencies (constructor parameters). The outer Dictionary is keyed by the dependency's
+ /// type, and the inner dictionary is keyed by the parameter name (optionally used to disambiguate parameters with the same type).
+ /// The inner dictionary value is the dependency.
+ ///
private Dictionary> _dependencies;
private readonly IFixture _fixture;
private readonly ConstructorParameterRelay _constructorParameterRelay;
@@ -23,9 +34,21 @@ public class SutProvider : ISutProvider
_fixture.Customizations.Add(_constructorParameterRelay);
}
+ ///
+ /// Registers a dependency to be injected when the sut is created. You must call after
+ /// this method to (re)create the sut with the dependency.
+ ///
+ /// The dependency to register.
+ /// An optional parameter name to disambiguate the dependency if there are multiple of the same type. You generally don't need this.
+ /// The type to register the dependency under - usually an interface. This should match the type expected by the sut's constructor.
+ ///
public SutProvider SetDependency(T dependency, string parameterName = "")
=> SetDependency(typeof(T), dependency, parameterName);
- public SutProvider SetDependency(Type dependencyType, object dependency, string parameterName = "")
+
+ ///
+ /// An overload for which takes a runtime object rather than a compile-time type.
+ ///
+ private SutProvider SetDependency(Type dependencyType, object dependency, string parameterName = "")
{
if (_dependencies.ContainsKey(dependencyType))
{
@@ -39,46 +62,69 @@ public class SutProvider : ISutProvider
return this;
}
+ ///
+ /// Gets a dependency of the sut. Can only be called after the SutProvider has been initialized with .
+ /// As dependencies are initialized with NSubstitute mocks by default, this is often used to retrieve those mocks in order to
+ /// configure them during the arrange stage, or check received calls in the assert stage.
+ ///
+ /// An optional parameter name to disambiguate the dependency if there are multiple of the same type. You generally don't need this.
+ /// The type of the dependency you want to get - usually an interface.
+ /// The dependency.
public T GetDependency(string parameterName = "") => (T)GetDependency(typeof(T), parameterName);
- public object GetDependency(Type dependencyType, string parameterName = "")
+
+ ///
+ /// An overload for which takes a runtime object rather than a compile-time type.
+ ///
+ private object GetDependency(Type dependencyType, string parameterName = "")
{
if (DependencyIsSet(dependencyType, parameterName))
{
return _dependencies[dependencyType][parameterName];
}
- else if (_dependencies.ContainsKey(dependencyType))
+
+ if (_dependencies.ContainsKey(dependencyType))
{
var knownDependencies = _dependencies[dependencyType];
if (knownDependencies.Values.Count == 1)
{
return _dependencies[dependencyType].Values.Single();
}
- else
- {
- throw new ArgumentException(string.Concat($"Dependency of type {dependencyType.Name} and name ",
- $"{parameterName} does not exist. Available dependency names are: ",
- string.Join(", ", knownDependencies.Keys)));
- }
- }
- else
- {
- throw new ArgumentException($"Dependency of type {dependencyType.Name} and name {parameterName} has not been set.");
+
+ throw new ArgumentException(string.Concat($"Dependency of type {dependencyType.Name} and name ",
+ $"{parameterName} does not exist. Available dependency names are: ",
+ string.Join(", ", knownDependencies.Keys)));
}
+
+ throw new ArgumentException($"Dependency of type {dependencyType.Name} and name {parameterName} has not been set.");
}
+ ///
+ /// Clear all the dependencies and the sut. This reverts the SutProvider back to a fully uninitialized state.
+ ///
public void Reset()
{
_dependencies = new Dictionary>();
Sut = default;
}
+ ///
+ /// Recreate a new sut with all new dependencies. This will reset all dependencies, including mocked return values
+ /// and any dependencies set with .
+ ///
public void Recreate()
{
_dependencies = new Dictionary>();
Sut = _fixture.Create();
}
+ /// >
ISutProvider ISutProvider.Create() => Create();
+
+ ///
+ /// Creates the sut, injecting any dependencies configured via and falling back to
+ /// NSubstitute mocks for any dependencies that have not been explicitly configured.
+ ///
+ ///
public SutProvider Create()
{
Sut = _fixture.Create();
@@ -90,6 +136,19 @@ public class SutProvider : ISutProvider
private object GetDefault(Type type) => type.IsValueType ? Activator.CreateInstance(type) : null;
+ ///
+ /// A specimen builder which tells Autofixture to use the dependency registered in
+ /// when creating test data. If no matching dependency exists in , it creates
+ /// an NSubstitute mock and registers it using
+ /// so it can be retrieved later.
+ /// This is the link between and Autofixture.
+ ///
+ ///
+ /// Autofixture knows how to create sample data of simple types (such as an int or string) but not more complex classes.
+ /// We create our own and register it with the in
+ /// to provide that instruction.
+ ///
+ /// The type of the sut.
private class ConstructorParameterRelay : ISpecimenBuilder
{
private readonly SutProvider _sutProvider;
@@ -103,6 +162,7 @@ public class SutProvider : ISutProvider
public object Create(object request, ISpecimenContext context)
{
+ // Basic checks to filter out irrelevant requests from Autofixture
if (context == null)
{
throw new ArgumentNullException(nameof(context));
@@ -117,16 +177,20 @@ public class SutProvider : ISutProvider
return new NoSpecimen();
}
+ // Use the dependency set under this parameter name, if any
if (_sutProvider.DependencyIsSet(parameterInfo.ParameterType, parameterInfo.Name))
{
return _sutProvider.GetDependency(parameterInfo.ParameterType, parameterInfo.Name);
}
- // Return default type if set
- else if (_sutProvider.DependencyIsSet(parameterInfo.ParameterType, ""))
+
+ // Use the default dependency set for this type, if any (i.e. no parameter name has been specified)
+ if (_sutProvider.DependencyIsSet(parameterInfo.ParameterType, ""))
{
return _sutProvider.GetDependency(parameterInfo.ParameterType, "");
}
+ // Fallback: create an NSubstitute mock (assuming the WithAutoNSubstitutions customization has been used)
+ // and register it using SetDependency so it can be retrieved later.
// This is the equivalent of _fixture.Create, but no overload for
// Create(Type type) exists.
var dependency = new SpecimenContext(_fixture).Resolve(new SeededRequest(parameterInfo.ParameterType,
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/SavePolicyCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/SavePolicyCommandTests.cs
index 3ca7004e70..426389f33c 100644
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/SavePolicyCommandTests.cs
+++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/SavePolicyCommandTests.cs
@@ -288,7 +288,7 @@ public class SavePolicyCommandTests
{
return new SutProvider()
.WithFakeTimeProvider()
- .SetDependency(typeof(IEnumerable), policyValidators ?? [])
+ .SetDependency(policyValidators ?? [])
.Create();
}