Is it possible to unit test BundleConfig in MVC4?
D

2

11

As far as I can tell, the answer is no. The issue I'm seeing comes from the Include(params string[]) method in the System.Web.Optimization.Bundle class. Internally this invokes System.Web.Optimization.IncludeDirectory(string, string, bool), which in turn uses this code:

DirectoryInfo directoryInfo = new DirectoryInfo(
    HttpContext.Current.Server.MapPath(directoryVirtualPath));

While it is possible to set HttpContext.Current during a unit test, I can't figure out how to make its .Server.MapPath(string directoryVirtualPath) return a non-null string. Since the DirectoryInfo(string) constructor throws an exception when passed a null argument, such a test will always fail.

What is the .NET team's recommendation for this? Do we have to unit test bundling configurations as part of integration tests or user acceptance tests?

Diabolism answered 7/7, 2012 at 2:3 Comment(1)
Really? Am I going to have to start a bounty on this one too???Diabolism
K
9

I have some good news for you, for RTM we added a new static property on BundleTable to enable more unit tests:

public static Func<string, string> MapPathMethod;

Edit Updated with a test virtual path provider:

So you can do something like this:

public class TestVirtualPathProvider : VirtualPathProvider {

    private string NormalizeVirtualPath(string virtualPath, bool isDirectory = false) {
        if (!virtualPath.StartsWith("~")) {
            virtualPath = "~" + virtualPath;
        }
        virtualPath = virtualPath.Replace('\\', '/');
        // Normalize directories to always have an ending "/"
        if (isDirectory && !virtualPath.EndsWith("/")) {
            return virtualPath + "/";
        }
        return virtualPath;
    }

    // Files on disk (virtualPath -> file)
    private Dictionary<string, VirtualFile> _fileMap = new Dictionary<string, VirtualFile>();
    private Dictionary<string, VirtualFile> FileMap {
        get { return _fileMap; }
    }

    public void AddFile(VirtualFile file) {
        FileMap[NormalizeVirtualPath(file.VirtualPath)] = file;
    }

    private Dictionary<string, VirtualDirectory> _directoryMap = new Dictionary<string, VirtualDirectory>();
    private Dictionary<string, VirtualDirectory> DirectoryMap {
        get { return _directoryMap; }
    }

    public void AddDirectory(VirtualDirectory dir) {
        DirectoryMap[NormalizeVirtualPath(dir.VirtualPath, isDirectory: true)] = dir;
    }

    public override bool FileExists(string virtualPath) {
        return FileMap.ContainsKey(NormalizeVirtualPath(virtualPath));
    }

    public override bool DirectoryExists(string virtualDir) {
        return DirectoryMap.ContainsKey(NormalizeVirtualPath(virtualDir, isDirectory: true));
    }

    public override VirtualFile GetFile(string virtualPath) {
        return FileMap[NormalizeVirtualPath(virtualPath)];
    }

    public override VirtualDirectory GetDirectory(string virtualDir) {
        return DirectoryMap[NormalizeVirtualPath(virtualDir, isDirectory: true)];
    }

    internal class TestVirtualFile : VirtualFile {
        public TestVirtualFile(string virtualPath, string contents)
            : base(virtualPath) {
            Contents = contents;
        }

        public string Contents { get; set; }

        public override Stream Open() {
            return new MemoryStream(UTF8Encoding.Default.GetBytes(Contents));
        }
    }

    internal class TestVirtualDirectory : VirtualDirectory {
        public TestVirtualDirectory(string virtualPath)
            : base(virtualPath) {
        }

        public List<VirtualFile> _directoryFiles = new List<VirtualFile>();
        public List<VirtualFile> DirectoryFiles {
            get {
                return _directoryFiles;
            }
        }

        public List<VirtualDirectory> _subDirs = new List<VirtualDirectory>();
        public List<VirtualDirectory> SubDirectories {
            get {
                return _subDirs;
            }
        }

        public override IEnumerable Files {
            get {
                return DirectoryFiles;
            }
        }

        public override IEnumerable Children {
            get { throw new NotImplementedException(); }
        }

        public override IEnumerable Directories {
            get { 
                return SubDirectories;
            }
        }
    }

And then write a unit test using that like so:

    [TestMethod]
    public void StyleBundleCustomVPPIncludeVersionSelectsTest() {
        //Setup the vpp to contain the files/directories
        TestVirtualPathProvider vpp = new TestVirtualPathProvider();
        var directory = new TestVirtualPathProvider.TestVirtualDirectory("/dir/");
        directory.DirectoryFiles.Add(new TestVirtualPathProvider.TestVirtualFile("/dir/style1.0.css", "correct"));
        directory.DirectoryFiles.Add(new TestVirtualPathProvider.TestVirtualFile("/dir/style.css", "wrong"));
        vpp.AddDirectory(directory);

        // Setup the bundle
        ScriptBundle bundle = new ScriptBundle("~/bundles/test");
        bundle.Items.VirtualPathProvider = vpp;
        bundle.Include("~/dir/style{version}.css");

        // Verify the bundle repsonse
        BundleContext context = SetupContext(bundle, vpp);
        BundleResponse response = bundle.GetBundleResponse(context);
        Assert.AreEqual(@"correct", response.Content);
    }
Kapoor answered 11/7, 2012 at 20:32 Comment(5)
Unfortunately, this workaround is no longer possible in the latest version because the MapPathMethod has been removed. Can you advise on an alternative strategy (possibly using a VirtualPathProvider)?Alexandretta
Looks like it changed again, there's no Items property in bundle anymore.Heterozygote
You can set the VPP on the BundleTable instead, bundle.Items is internal.Kapoor
@HaoKung, I tried following your suggestions about unit testing the bundle but whenever I try to enumerate the bundle files, I always get zero count. Can you have a look at my question please? #25931110Aba
I got this sample to work for me but if I remove the {version} wildcard and update the virtual file to style.css instead of style1.0.css it doesn't work. I tried to step through the mock classes but I think it's the framework that does the wildcard matching. Any help would be greatly appreciated.Grillroom
P
7

In .Net 4.5 things have slightly changed. Here is a working version of the approved answer updated to accommodate these changes (I am using Autofac). Note the "GenerateBundleResponse" instead of "GetBundleResponse":

    [Fact]
    public void StyleBundleIncludesVersion()
    {
        //Setup the vpp to contain the files/directories
        var vpp = new TestVirtualPathProvider();
        var directory = new TestVirtualPathProvider.TestVirtualDirectory("/dir/");
        directory.DirectoryFiles.Add(new TestVirtualPathProvider.TestVirtualFile("/dir/style1.0.css", "correct"));
        directory.DirectoryFiles.Add(new TestVirtualPathProvider.TestVirtualFile("/dir/style.css", "wrong"));
        vpp.AddDirectory(directory);

        // Setup the bundle
        var bundleCollection = new BundleCollection();
        var bundle = new ScriptBundle("~/bundles/test");
        BundleTable.VirtualPathProvider = vpp;
        bundle.Include("~/dir/style{version}.css");
        bundleCollection.Add(bundle);
        var mockHttpContext = new Mock<HttpContextBase>();

        // Verify the bundle repsonse
        var context = new BundleContext(mockHttpContext.Object, bundleCollection, vpp.ToString());
        var response = bundle.GenerateBundleResponse(context);
        Assert.Equal(@"correct", response.Content);
    }
Py answered 1/3, 2014 at 17:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.