Question

EDIT: I've decided to ditch the idea completely because while it's nice to use the same method to create my instances I'm giving up for other things and complicate the problem.

In my Unit Test projects I have a folder that basically contain all the factories classes for my unit tests such as the one below.

public static class PackageFileInfoFactory
{
    private const string FILE_NAME = "testfile.temp";

    public static PackageFileInfo CreateFullTrustInstance()
    {
        var mockedFileInfo = CreateMockedInstance(AspNetHostingPermissionLevel.Unrestricted);

        return mockedFileInfo.Object;
    }

    public static PackageFileInfo CreateMediumTrustInstance()
    {
        var mockedFileInfo = CreateMockedInstance(AspNetHostingPermissionLevel.Medium);

        return mockedFileInfo.Object;
    }

    private static Mock<PackageFileInfo> CreateMockedInstance(AspNetHostingPermissionLevel trustLevel)
    {
        var mockedFileInfo = new Mock<PackageFileInfo>(FILE_NAME);

        mockedFileInfo.Protected().SetupGet<AspNetHostingPermissionLevel>("TrustLevel").Returns(() => trustLevel);

        mockedFileInfo.Protected().Setup<string>("CopyTo", ItExpr.IsAny<string>()).Returns<string>(destFileName => "Some Unknown Path");

        return mockedFileInfo;
    }
}

Here is a sample of my Unit Test.

public class PackageFileInfoTest
{
    public class Copy
    {
        [Fact]
        public void Should_throw_exception_in_medium_trust_when_probing_directory_does_not_exist()
        {
            // Arrange
            var fileInfo = PackageFileInfoFactory.CreateMediumTrustInstance();

            fileInfo.ProbingDirectory = "SomeDirectory";

            // Act
            Action act = () => fileInfo.Copy();

            // Assert
            act.ShouldThrow<InvalidOperationException>();
        }

        [Fact]
        public void Should_throw_exception_in_full_trust_when_probing_directory_does_not_exist()
        {
            // Arrange
            var fileInfo = PackageFileInfoFactory.CreateFullTrustInstance();

            fileInfo.ProbingDirectory = "SomeDirectory";

            // Act
            Action act = () => fileInfo.Copy();

            // Assert
            act.ShouldThrow<InvalidOperationException>();
        }

        [Fact]
        public void Should_throw_exception_when_probing_directory_is_null_or_empty()
        {
            // Arrange
            var fileInfo = PackageFileInfoFactory.CreateFullTrustInstance();

            // Act
            Action act = () => fileInfo.Copy();

            // Assert
            act.ShouldThrow<InvalidOperationException>();
        }
    }
}

This helps me to keep my unit tests clean and focus on the tests, I just wondering how other people handle this and what are they doing to keep the tests clean.

UPDATE:

In response to Adronius I've updated my post with a prototype of what I'm aiming at to reduce these factories.

A major concern is to have the exact same syntax across my tests to create instances and reduce the amount of factory classes.

namespace EasyFront.Framework.Factories
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;

    using EasyFront.Framework.Diagnostics.Contracts;

    /// <summary>
    ///     Provides a container to objects that enable you to setup them and resolve instances by type.
    /// </summary>
    /// <remarks>
    ///     Eyal Shilony, 20/07/2012.
    /// </remarks>
    public class ObjectContainer
    {
        private readonly Dictionary<string, object> _registeredTypes;

        public ObjectContainer()
        {
            _registeredTypes = new Dictionary<string, object>();
        }

        public TResult Resolve<TResult>()
        {
            string keyAsString = typeof(TResult).FullName;

            return Resolve<TResult>(keyAsString);
        }

        public void AddDelegate<TResult>(Func<TResult> func)
        {
            Contract.Requires(func != null);

            Add(typeof(TResult).FullName, func);
        }

        protected virtual TResult Resolve<TResult>(string key)
        {
            Contract.Requires(!string.IsNullOrEmpty(key));

            if (ContainsKey(key))
            {
                Func<TResult> func = GetValue<Func<TResult>>(key);

                Assert.NotNull(func);

                return func();
            }

            ThrowWheNotFound<TResult>();

            return default(TResult);
        }

        protected void Add<T>(string key, T value) where T : class
        {
            Contract.Requires(!string.IsNullOrEmpty(key));

            _registeredTypes.Add(key, value);
        }

        protected bool ContainsKey(string key)
        {
            Contract.Requires(!string.IsNullOrEmpty(key));

            return _registeredTypes.ContainsKey(key);
        }

        protected T GetValue<T>(string key) where T : class
        {
            Contract.Requires(!string.IsNullOrEmpty(key));

            return _registeredTypes[key] as T;
        }

        protected void ThrowWheNotFound<TResult>()
        {
            throw new InvalidOperationException(string.Format("The type '{0}' was not found in type '{1}'.", typeof(TResult).FullName, GetType().ReflectedType.FullName));
        }

        [ContractInvariantMethod]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(_registeredTypes != null);
        }

    }
}

I can then extend this to take a parameter like this.

namespace EasyFront.Framework.Factories
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;

    using EasyFront.Framework.Diagnostics.Contracts;

    public class ObjectContainer<T> : ObjectContainer
    {
        public TResult Resolve<TResult>(T key = default(T))
        {
            string keyAsString = EqualityComparer<T>.Default.Equals(key, default(T)) ? typeof(TResult).FullName : GetKey(key);

            Assert.NotNullOrEmpty(keyAsString);

            return Resolve<TResult>(keyAsString);
        }

        public void AddDelegate<TReturn>(T key, Func<T, TReturn> func)
        {
            Contract.Requires(func != null);

            Add(GetKey(key), func);
        }

        protected override TResult Resolve<TResult>(string key)
        {
            if (ContainsKey(key))
            {
                Func<TResult> func = GetValue<Func<TResult>>(key);

                Assert.NotNull(func);

                return func();
            }

            throw new InvalidOperationException(string.Format("The type '{0}' was not setup for type '{1}'.", typeof(TResult).FullName, GetType().ReflectedType.FullName));
        }

        /// <summary> Gets the full name of the type and the hash code as the key. </summary>
        /// <remarks> Eyal Shilony, 20/07/2012. </remarks>
        /// <param name="value"> The value to use to get key. </param>
        /// <returns> The full name of the type and the hash code as the key. </returns>
        private static string GetKey(T value)
        {
            Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));

            string key = value.GetType().FullName + "#" + value.ToString().GetHashCode();

            Assert.NotNullOrEmpty(key);

            return key;
        }
    }
}

The implementation would be something like this.

namespace EasyFront.Tests.Factories
{
    using System.Web;

    using EasyFront.Framework.Factories;
    using EasyFront.Framework.Web.Hosting.Packages;

    using Moq;
    using Moq.Protected;

    public class PackageFileInfoFactory : IObjectFactory<AspNetHostingPermissionLevel>
    {
        private const string FILE_NAME = "testfile.temp";

        private readonly ObjectContainer<AspNetHostingPermissionLevel> _container;

        public PackageFileInfoFactory()
        {
            _container = new ObjectContainer<AspNetHostingPermissionLevel>();

            _container.AddDelegate(AspNetHostingPermissionLevel.Unrestricted, value =>
            {
                var mockedFileInfo = CreateMockedInstance(value);

                return mockedFileInfo.Object;
            });

            _container.AddDelegate(AspNetHostingPermissionLevel.Medium, value =>
            {
                var mockedFileInfo = CreateMockedInstance(value);

                return mockedFileInfo.Object;
            });
        }

        public TResult CreateInstance<TResult>(AspNetHostingPermissionLevel first)
        {
            return _container.Resolve<TResult>(first);
        }

        private static Mock<PackageFileInfo> CreateMockedInstance(AspNetHostingPermissionLevel trustLevel)
        {
            var mockedFileInfo = new Mock<PackageFileInfo>(FILE_NAME);

            mockedFileInfo.Protected().SetupGet<AspNetHostingPermissionLevel>("TrustLevel").Returns(() => trustLevel);

            mockedFileInfo.Protected().Setup<string>("CopyTo", ItExpr.IsAny<string>()).Returns<string>(destFileName => "Some Unknown Path");

            return mockedFileInfo;
        }
    }
}

and finally I can use it like this.

namespace EasyFront.Framework.Web.Hosting.Packages
{
    using System;
    using System.Web;

    using EasyFront.Tests.Factories;

    using FluentAssertions;

    using global::Xunit;

    public class PackageFileInfoTest
    {
        public class Copy
        {
            private readonly PackageFileInfoFactory _factory;

            public Copy()
            {
                _factory = new PackageFileInfoFactory();
            }

            [Fact]
            public void Should_throw_exception_in_medium_trust_when_probing_directory_does_not_exist()
            {
                // Arrange
                var fileInfo = _factory.CreateInstance<PackageFileInfo>(AspNetHostingPermissionLevel.Medium);

                fileInfo.ProbingDirectory = "SomeDirectory";

                // Act
                Action act = () => fileInfo.Copy();

                // Assert
                act.ShouldThrow<InvalidOperationException>();
            }

            [Fact]
            public void Should_throw_exception_in_full_trust_when_probing_directory_does_not_exist()
            {
                // Arrange
                var fileInfo = _factory.CreateInstance<PackageFileInfo>(AspNetHostingPermissionLevel.Unrestricted);

                fileInfo.ProbingDirectory = "SomeDirectory";

                // Act
                Action act = () => fileInfo.Copy();

                // Assert
                act.ShouldThrow<InvalidOperationException>();
            }

            [Fact]
            public void Should_throw_exception_when_probing_directory_is_null_or_empty()
            {
                // Arrange
                var fileInfo = _factory.CreateInstance<PackageFileInfo>(AspNetHostingPermissionLevel.Unrestricted);

                // Act
                Action act = () => fileInfo.Copy();

                // Assert
                act.ShouldThrow<InvalidOperationException>();
            }
        }
    }
}

I'm don't know whether it's working or not it's just a concept I made for the post to demonstrate my point, I wonder what people think about it? if you have any suggestions or anything I'll be glad to hear about it.

I don't like reinventing the wheel so if you have a better approach I'd like to hear about it too. :)

Was it helpful?

Solution

I was not able to understand the test-factory-class-framework in your update within 2 minutes so in my opinion it does not make maintaining the tests easier.

However i like your concept of having a centralised testdatagenerator.

In my opinion one test-data-factory-method per type should be enough in most cases. This method defines the standard-Testdata for the type.

The actual test assigns the difference to the standard-testdata.

Simple examle:

public class PackageFileInfoTest
{
    public class Copy
    {
        [Fact]
        public void Should_throw_exception_when_probing_directory_does_not_exist()
        {
            // Arrange
            var fileInfo = TestData.CreatePackageFileInfo();
            fileInfo.ProbingDirectory = "/Some/Directory/That/Does/Not/Exist";
            // Act
            Action act = () => fileInfo.Copy();

            // Assert
            act.ShouldThrow<InvalidOperationException>();
        }

note: a "non existing directory test" should be independant of "permission level". Therefore the standard-permission of the factory should be enaugh.

A more complex examle of one factorymethod per type when more than one type is involved:

        [Fact]
        public void Should_throw_exception_in_low_trust_when_writing_to_protected_directory()
        {
            // Arrange
            var protectedDirectory = TestData.CreateDirectory();
            protectedDirectory.MinimalPermissionRequired = AspNetHostingPermissionLevel.Full;

            var currentUser= TestData.CreateUser();
            currentUser.TrustLevel = AspNetHostingPermissionLevel.Low;

            // Act
            Action act = () => FileUploadService.CopyTo(protectedDirectory);

            ....
    }
}

In the Dotnet-world nbuilder can help you to populate arrays of testdata.

OTHER TIPS

I used the almost same approach in my unit tests.

If I have duplicities in one test fixture class, most of creation (not initialization) of mocks(doubles) I put into setUp method.

But there are still duplicities among several test fixture classes. So for those duplicities I use "TestDoubleFactory" static class that looks similar as yours, except I don't have methods to create instances, but I always create only mock objects so I can modify(setup) them in tests further.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top