Search code examples
asp.netunit-testingmodel-view-controllerdeploymentbundling-and-minification

Automatically detect minification failures in ASP.NET MVC bundling


From time to time I notice the following error message on the top of my CSS bundle produced by ASP.NET MVC:

/* Minification failed. Returning unminified contents.
(7933,26): run-time error CSS1019: Unexpected token, found ':'
(7933,26): run-time error CSS1042: Expected function, found ':'
(7933,26): run-time error CSS1062: Expected semicolon or closing curly-brace, found ':'
(7934,30): run-time error CSS1019: Unexpected token, found ':'
(7934,30): run-time error CSS1042: Expected function, found ':'
(7934,30): run-time error CSS1062: Expected semicolon or closing curly-brace, found ':'
 */

These errors always go silently through the build, deployment and unit tests and are very hard to notice. Is there any solution to automatically catch them? It is hard to get that from the unit test as there's no content folder being copied to the unit test project. Preferably this should fail the build or at least unit test.


Solution

  • I had the same issue and wanted to unit test the minification, after some false starts trying to mock the BundleContext, I opted to just directly test the minification provided by webgrease Microsoft.Ajax.Utilities.Minifier, slight issue was the BundleItem is internal to System.Web.Optimization so I had to do reflection:

      //arrange
      BundleCollection bundles = new BundleCollection();
    
      // my static bundler config in mvc
      BundleConfig.RegisterBundles(bundles); 
    
      // act and assert
      Assert.Multiple(() =>
      {
        foreach (var bundle in bundles)
        {
          // CAUTION: Reflection to private member!
          var items = typeof(Bundle).GetProperty("Items", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(bundle) as IEnumerable<Object>;
          foreach (Object item in items)
          {
            var minifier = new Minifier();
            var type = item.GetType();
            var path = type.GetProperty("VirtualPath")?.GetValue(item) as String;
    
            // todo: handle "BundleDirectoryItem" too ...
            if (type.Name == "BundleItem")
            {
              // todo: transform your virtual path to a physical path to the file at design time
              var file = File.ReadAllText(path);
              String min;
              if (path.EndsWith("css"))
              {
                min = minifier.MinifyStyleSheet(file);
              }
              else
              {
                min = minifier.MinifyJavaScript(file);
              }
              Assert.IsNotNull(min, $"Failed to get minified output for '{path}'");
              Assert.Zero(minifier.Errors.Count, $"Minification failed for '{path}', errors:\r\n{String.Join("\r\n", minifier.Errors)}");
            }
          }
        }
      });