From d08e9359af3c80bb43dd976387b1289605cb5467 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Fri, 29 Oct 2021 18:37:37 -0400 Subject: [PATCH] Enable testing of ASP.net MVC controllers Controller properties have all kinds of validations in the background. In general, we don't user properties on our Controllers, so the easiest way to allow for Autofixture-based testing of our Controllers is to just omit setting all properties on them. --- test/Api.Test/Api.Test.csproj | 1 + .../AutoFixture/ControllerCustomization.cs | 38 +++++++++++++++++ .../Attributes/BitCustomizeAttribute.cs | 22 ++++++++++ .../BuilderWithoutAutoProperties.cs | 41 +++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 test/Api.Test/AutoFixture/ControllerCustomization.cs create mode 100644 test/Common/AutoFixture/Attributes/BitCustomizeAttribute.cs create mode 100644 test/Common/AutoFixture/BuilderWithoutAutoProperties.cs diff --git a/test/Api.Test/Api.Test.csproj b/test/Api.Test/Api.Test.csproj index e8c71e9f23..dd902a33d0 100644 --- a/test/Api.Test/Api.Test.csproj +++ b/test/Api.Test/Api.Test.csproj @@ -22,6 +22,7 @@ + diff --git a/test/Api.Test/AutoFixture/ControllerCustomization.cs b/test/Api.Test/AutoFixture/ControllerCustomization.cs new file mode 100644 index 0000000000..137cadb895 --- /dev/null +++ b/test/Api.Test/AutoFixture/ControllerCustomization.cs @@ -0,0 +1,38 @@ +using AutoFixture; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc; +using Bit.Api.Controllers; +using AutoFixture.Kernel; +using System; +using Bit.Test.Common.AutoFixture; +using Org.BouncyCastle.Security; + +namespace Bit.Api.Test.AutoFixture +{ + /// + /// Disables setting of Auto Properties on the Controller to avoid ASP.net initialization errors. Still sets constructor dependencies. + /// + /// + public class ControllerCustomization : ICustomization + { + private readonly Type _controllerType; + public ControllerCustomization(Type controllerType) + { + if (!controllerType.IsAssignableTo(typeof(Controller))) + { + throw new InvalidParameterException($"{nameof(controllerType)} must derive from {typeof(Controller).Name}"); + } + + _controllerType = controllerType; + } + + public void Customize(IFixture fixture) + { + fixture.Customizations.Add(new BuilderWithoutAutoProperties(_controllerType)); + } + } + public class ControllerCustomization : ICustomization where T : Controller + { + public void Customize(IFixture fixture) => new ControllerCustomization(typeof(T)).Customize(fixture); + } +} diff --git a/test/Common/AutoFixture/Attributes/BitCustomizeAttribute.cs b/test/Common/AutoFixture/Attributes/BitCustomizeAttribute.cs new file mode 100644 index 0000000000..32910ef537 --- /dev/null +++ b/test/Common/AutoFixture/Attributes/BitCustomizeAttribute.cs @@ -0,0 +1,22 @@ +using System; +using AutoFixture; + +namespace Bit.Test.Common.AutoFixture.Attributes +{ + /// + /// + /// Base class for customizing parameters in methods decorated with the + /// Bit.Test.Common.AutoFixture.Attributes.MemberAutoDataAttribute. + /// + /// ⚠ Warning ⚠ Will not insert customizations into AutoFixture's AutoDataAttribute build chain + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Parameter, AllowMultiple = true)] + public abstract class BitCustomizeAttribute : Attribute + { + /// + /// /// Gets a customization for the method's parameters. + /// + /// A customization for the method's paramters. + public abstract ICustomization GetCustomization(); + } +} diff --git a/test/Common/AutoFixture/BuilderWithoutAutoProperties.cs b/test/Common/AutoFixture/BuilderWithoutAutoProperties.cs new file mode 100644 index 0000000000..81df3206a0 --- /dev/null +++ b/test/Common/AutoFixture/BuilderWithoutAutoProperties.cs @@ -0,0 +1,41 @@ +using System; +using AutoFixture; +using AutoFixture.Dsl; +using AutoFixture.Kernel; + +namespace Bit.Test.Common.AutoFixture +{ + public class BuilderWithoutAutoProperties : ISpecimenBuilder + { + private readonly Type _type; + public BuilderWithoutAutoProperties(Type type) + { + _type = type; + } + + public object Create(object request, ISpecimenContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var type = request as Type; + if (type == null || type != _type) + { + return new NoSpecimen(); + } + + var fixture = new Fixture(); + // This is the equivalent of _fixture.Build<_type>().OmitAutoProperties().Create(request, context), but no overload for + // Build(Type type) exists. + dynamic reflectedComposer = typeof(Fixture).GetMethod("Build").MakeGenericMethod(_type).Invoke(fixture, null); + return reflectedComposer.OmitAutoProperties().Create(request, context); + } + } + public class BuilderWithoutAutoProperties : ISpecimenBuilder + { + public object Create(object request, ISpecimenContext context) => + new BuilderWithoutAutoProperties(typeof(T)).Create(request, context); + } +}