NUnit DeploymentItem

I've done a few improvements to Alexander Pasha's solution: I've given the attribute the same signature as the MSTest one, such that the first parameter is the absolute or relative file or folder to deploy, and the optional second parameter is the absolute or relative path to which it is to be deployed. In both cases 'relative' means to the executing program. Also I've removed any read-only attribute from the deployed file. This is important - if a previously deployed file can't be overwritten the attribute will throw. It is also worth noting that MSTest and NUnit have very different strategies when it comes to deploying the files that are to be used during testing. MSTest may or may not copy files to a deployment folder - see here. NUnit uses the ShadowCopyFiles property of the AppDomain, which deploys to a very obscure location in the user's temp folder. While shadow copying can be turned on and off in NUnit itself, I don't know how to manipulate it when using Test Explorer in Visual Studio. In this regard it is important to note that the Visual Studio NUnit Test Adapter prior to Version 2 has shadow copying turned on but in version 2 onwards it is turned off. This can have a major impact on tests that use deployment items and is well worth being aware of. Here is my version of the DeploymentItemAttribute:-

namespace NUnitDeploymentItem
{
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = false)]
    public class DeploymentItemAttribute : Attribute
    {
        /// <summary>
        /// NUnit replacement for Microsoft.VisualStudio.TestTools.UnitTesting.DeploymentItemAttribute
        /// Marks an item to be relevant for a unit-test and copies it to deployment-directory for this unit-test.
        /// </summary>
        /// <param name="path">The relative or absolute path to the file or directory to deploy. The path is relative to the build output directory.</param>
        /// <param name="outputDirectory">The path of the directory to which the items are to be copied. It can be either absolute or relative to the deployment directory.</param>
        public DeploymentItemAttribute(string path, string outputDirectory = null)
        {
            // Escape input-path to correct back-slashes for Windows
            string filePath = path.Replace("/", "\\");

            // Look up where we are right now
            DirectoryInfo environmentDir = new DirectoryInfo(Environment.CurrentDirectory);

            // Get the full path and name of the deployment item
            string itemPath = new Uri(Path.Combine(environmentDir.FullName, filePath)).LocalPath;
            string itemName = Path.GetFileName(itemPath);

            // Get the target-path where to copy the deployment item to
            string binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

            // NUnit uses an obscure ShadowCopyCache directory which can be hard to find, so let's output it so the poor developer can get at it more easily
            Debug.WriteLine("DeploymentItem: Copying " + itemPath + " to " + binFolderPath);

            // Assemble the target path
            string itemPathInBin;
            if (string.IsNullOrEmpty(outputDirectory))
            {
                itemPathInBin = new Uri(Path.Combine(binFolderPath, itemName)).LocalPath;
            }
            else if (!string.IsNullOrEmpty(Path.GetPathRoot(outputDirectory)))
            {
                itemPathInBin = new Uri(Path.Combine(outputDirectory, itemName)).LocalPath;
            }
            else
            {
                itemPathInBin = new Uri(Path.Combine(binFolderPath, outputDirectory, itemName)).LocalPath;
            }

            // Decide whether it's a file or a folder
            if (File.Exists(itemPath)) // It's a file
            {
                // Assemble the parent folder path (because the item might be in multiple sub-folders.
                string parentFolderPathInBin = new DirectoryInfo(itemPathInBin).Parent.FullName;

                // If the target directory does not exist, create it
                if (!Directory.Exists(parentFolderPathInBin))
                {
                    Directory.CreateDirectory(parentFolderPathInBin);
                }

                // copy source-file to the destination
                File.Copy(itemPath, itemPathInBin, true);

                // We must allow the destination file to be deletable
                FileAttributes fileAttributes = File.GetAttributes(itemPathInBin);
                if ((fileAttributes & FileAttributes.ReadOnly) != 0)
                {
                    File.SetAttributes(itemPathInBin, fileAttributes & ~FileAttributes.ReadOnly);
                }
            }
            else if (Directory.Exists(itemPath)) // It's a folder
            {
                // If it already exists, remove it
                if (Directory.Exists(itemPathInBin))
                {
                    Directory.Delete(itemPathInBin, true);
                }

                // Create target directory
                Directory.CreateDirectory(itemPathInBin);

                // Now Create all of the sub-directories
                foreach (string dirPath in Directory.GetDirectories(itemPath, "*", SearchOption.AllDirectories))
                {
                    Directory.CreateDirectory(dirPath.Replace(itemPath, itemPathInBin));
                }

                //Copy all the files & Replace any files with the same name
                foreach (string sourcePath in Directory.GetFiles(itemPath, "*.*", SearchOption.AllDirectories))
                {
                    string destinationPath = sourcePath.Replace(itemPath, itemPathInBin);
                    File.Copy(sourcePath, destinationPath, true);

                    // We must allow the destination file to be deletable
                    FileAttributes fileAttributes = File.GetAttributes(destinationPath);
                    if ((fileAttributes & FileAttributes.ReadOnly) != 0)
                    {
                        File.SetAttributes(destinationPath, fileAttributes & ~FileAttributes.ReadOnly);
                    }
                }
            }
            else
            {
                Debug.WriteLine("Warning: Deployment item does not exist - \"" + itemPath + "\"");
            }
        }
    }
}

You should check out another thread that contrasts the capabilities of NUnit and MSTest.

The accepted answer here is misleading. NUnit does not offer the [DeploymentItem("")] attribute at all which is what @Idsa wanted an equivalent solution for in NUnit.

My guess is that this kind of attribute would violate the scope of NUnit as a "unit" testing framework as requiring an item to be copied to the output before running a test implies it has a dependency on this resource being available.

I'm using a custom attribute to copy over a localdb instance for running "unit" tests against some sizeable test data that I'd rather not generate with code everytime.

Now using the attribute [DeploymentItem("some/project/file")] will copy this resource from file system into the bin again effectively refreshing my source data per test method:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Struct, 
    AllowMultiple = false, 
    Inherited = false)]
public class DeploymentItem : System.Attribute {
    private readonly string _itemPath;
    private readonly string _filePath;
    private readonly string _binFolderPath;
    private readonly string _itemPathInBin;
    private readonly DirectoryInfo _environmentDir;
    private readonly Uri _itemPathUri;
    private readonly Uri _itemPathInBinUri;

    public DeploymentItem(string fileProjectRelativePath) {
        _filePath = fileProjectRelativePath.Replace("/", @"\");

        _environmentDir = new DirectoryInfo(Environment.CurrentDirectory);
        _itemPathUri = new Uri(Path.Combine(_environmentDir.Parent.Parent.FullName
            , _filePath));

        _itemPath = _itemPathUri.LocalPath;
        _binFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

        _itemPathInBinUri = new Uri(Path.Combine(_binFolderPath, _filePath));
        _itemPathInBin = _itemPathInBinUri.LocalPath;

        if (File.Exists(_itemPathInBin)) {
            File.Delete(_itemPathInBin);
        }

        if (File.Exists(_itemPath)) {
            File.Copy(_itemPath, _itemPathInBin);
        }
    }
}

Then we can use like so:

[Test]
[DeploymentItem("Data/localdb.mdf")]
public void Test_ReturnsTrue() 
{
    Assert.IsTrue(true);
}

Tags:

.Net

Nunit