Thursday, January 31, 2013

Mvc4 unit(?) testing of views with BDD & SpecFlow

I wanted to unit test my mvc. I had no idea what that could mean. Not my pocos or models, my Mvc. After googling what are the primary targets taken by unit testers? Controllers, almost never Views. The level of complexity behind getting a test framework given v as our view to do v.ExecuteResult() seems over the top immense. The spec flow: Feature: Register a new User In order to register a new User As member of the site So that they can log in to the site and use its features Scenario: Browse Register page When the user goes to the register user screen Then the register user view should be displayed And the view should have a username input field The MsTest class:
using System;
using System.Web.Mvc;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using MvcContrib.TestHelper;

using MvcSpecFlow.Controllers;
using MvcSpecFlow.Views.Account;

using RazorGenerator.Testing;

using TechTalk.SpecFlow;

//http://www.codeproject.com/Articles/82891/BDD-using-SpecFlow-on-ASP-NET-MVC-Application
//http://blog.davidebbo.com/2011/06/precompile-your-mvc-views-using.html
//http://blog.davidebbo.com/2011/06/unit-test-your-mvc-views-using-razor.html

namespace MvcSpecFlow.Tests
{
  [Binding]
  public class RegisterUserSteps
  {
    ActionResult result;

    AccountController controller;

    [When(@"the user goes to the register user screen")]
    public void WhenTheUserGoesToTheRegisterUserScreen()
    {
      controller = new AccountController();
      result = controller.Register();

    }

    [Then(@"the register user view should be displayed")]
    public void ThenTheRegisterUserViewShouldBeDisplayed()
    {
      Assert.IsInstanceOfType(result, typeof(ViewResult));

      var vResult = (ViewResult)result;
      Assert.AreEqual(string.Empty, vResult.ViewName);

      vResult.AssertViewRendered().ForView(string.Empty); //should follow convention not pass a special view name
      
    }

    [Then(@"the view should have a username input field")]
    public void ThenTheViewShouldHaveAUsernameInputField()
    {
      var view = new Register();
      var doc = view.RenderAsHtml();
      var username = doc.GetElementbyId("UserName");
      Assert.IsNotNull(username);
    
    }
  }
}
The requirements:
  1. Use RazorGenerator to precompile your views
  2. The view you are testing has the RazorGenerator set as the Custom Tool
  3. The view you are testing does not have the following razor: @Html.AntiForgeryToken()
  4. Your testing project has RazorGenerator.Testing installed.

Wednesday, January 30, 2013

CORS without preflight

I had some trouble getting a CORS request to fire without preflight I finally got it and here it is In raw javascript:
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.open("GET", "http://jira/rest/api/latest/issue/DEV-793", false);
  xmlhttp.send();
  //exception (as expected) for 401 response from that site
  document.getElementById("myDiv").innerHTML = xmlhttp.responseText;
since the server is not responding with the proper response header the browser won't let me read the response for security reasons, but at least the send side is working currently. This also works for send (still has the issue with the browser blocking the response from the javascript:
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.open("GET", "http://jira/rest/api/latest/issue/DEV-793", false);
  xmlhttp.setRequestHeader("accept", "application/json");
  xmlhttp.send();
  document.getElementById("myDiv").innerHTML = xmlhttp.responseText;
I can't get jQuery to do it in Chrome whatsoever. Posted a SO question. Perhaps this is the problem, a bug in chrome Also I've found that I don't need to muck with the server if I set the command line option for chrome `http://stackoverflow.com/a/13154327/57883`

Monday, January 14, 2013

Clean your EDMX programmatically

So you have pesky underscores in your edmx, that appear every time you add a new property, table, view, relationship, or whatever. Here's the cleaning code based on Inflector and some help from a co-worker. `.Dump` is just a generic print out command available in linqpad, the code would work without those method calls.
void Main()
{
  var edmx= @"C:\Projects\psh\hpx\src\Entities\JobSystem\JobSystem.edmx";
  var xdoc=System.Xml.Linq.XDocument.Load(edmx);
  xdoc.Dump();
  var ns=xdoc.Root.Name.Namespace;
  
  //sort entity types?
  
  var storageModels=xdoc.Root.Element(ns+"Runtime").Element(ns+"StorageModels");
  var storeNs=storageModels.GetNamespaceOfPrefix("store");
  
  new {Root=ns,Default=xdoc.Root.GetDefaultNamespace(),DefaultLocal=storageModels.GetDefaultNamespace(), Store=storeNs}.Dump("namespaces");
  
  var schemas=storageModels.Elements();
  
  Debug.Assert(schemas.All (s => s.Name.LocalName=="Schema"));
  //ProcessSchemas(schemas); //not helpful or necessary
  var mappings = xdoc.Root.Element(ns+"Runtime").Element(ns+"Mappings").Elements();
  Debug.Assert(mappings.All (m => m.Name.LocalName=="Mapping"));
  ProcessMappings(mappings);
  var concept= xdoc.Root.Element(ns+"Runtime").Element(ns+"ConceptualModels");
  
  ProcessConceptual(concept);
  xdoc.Save(edmx);    
    
  
}

// Define other methods and classes here
void ProcessConceptual(XElement concept)
{
  var processed= new List<string>();
      var localNs=concept.Elements().First ().Name.Namespace;
    foreach(var s in concept.Elements())
    {
  
      localNs.Dump("Entity Type?");
      
      foreach(var et in s.Elements(localNs+"EntityType"))
      {
        foreach(var p in et.Elements(localNs+"Property"))
        {
          var pName=p.Attribute(XNamespace.None+"Name");  
          var existing=pName.Value;
          var proposed=existing.Pascalize();
          if(existing != proposed){
            processed.Add(et.Attribute(XNamespace.None+"Name").Value+":"+existing);
            pName.Value=proposed;
          }
        }
        
      }
      
    }
    var propRefs=concept.XPathSelectElements(".//*[local-name()='PropertyRef']");
    
    foreach(var propRef in propRefs)
    {
      var pName=propRef.Attribute(XNamespace.None+"Name");  
          var existing=pName.Value;
          var proposed=existing.Pascalize();
          if(existing != proposed){
            processed.Add(propRef.Parent.Name+":"+existing);
            pName.Value=proposed;
          }
    }
  
  processed.Dump("Conceptuals");
}
void ProcessMappings(IEnumerable<XElement> mappings)
{
  var processed=new List<string>();
  foreach(var m in mappings)
  {
    var localNs=m.Name.Namespace;
    foreach(var ecm in m.Elements(localNs+"EntityContainerMapping"))
    foreach(var esm in ecm.Elements(localNs+"EntitySetMapping"))
    foreach(var etm in esm.Elements(localNs+"EntityTypeMapping"))
    {
      var typeName=etm.Attribute(XNamespace.None+"TypeName");
      foreach(var mf in etm.Elements(localNs+"MappingFragment"))
      foreach(var sp in mf.Elements(localNs+"ScalarProperty"))
      {
        var spName=sp.Attribute(XNamespace.None+"Name");  
        var existing=spName.Value;
        var proposed=existing.Pascalize();
        if(existing != proposed){
          processed.Add(typeName+":"+existing);
          spName.Value=proposed;
        }
      }
    }
  }
  processed.Dump();
}


Inflector is just awesome available as a nuget package if you don't need it to be strongly signed.
/// <summary>
  /// Inflector NuGet package is not strong signed =(
  /// </summary>
  public static class Inflector
{
    readonly static List<Rule> _plurals;

    readonly static List<Rule> _singulars;

    readonly static List<string> _uncountables;

    static Inflector()
    {
        _plurals = new List<Rule>();
        _singulars = new List<Rule>();
        _uncountables = new List<string>();
        AddPlural("$", "s");
        AddPlural("s$", "s");
        AddPlural("(ax|test)is$", "$1es");
        AddPlural("(octop|vir|alumn|fung)us$", "$1i");
        AddPlural("(alias|status)$", "$1es");
        AddPlural("(bu)s$", "$1ses");
        AddPlural("(buffal|tomat|volcan)o$", "$1oes");
        AddPlural("([ti])um$", "$1a");
        AddPlural("sis$", "ses");
        AddPlural("(?:([^f])fe|([lr])f)$", "$1$2ves");
        AddPlural("(hive)$", "$1s");
        AddPlural("([^aeiouy]|qu)y$", "$1ies");
        AddPlural("(x|ch|ss|sh)$", "$1es");
        AddPlural("(matr|vert|ind)ix|ex$", "$1ices");
        AddPlural("([m|l])ouse$", "$1ice");
        AddPlural("^(ox)$", "$1en");
        AddPlural("(quiz)$", "$1zes");
        AddSingular("s$", string.Empty);
        AddSingular("(n)ews$", "$1ews");
        AddSingular("([ti])a$", "$1um");
        AddSingular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis");
        AddSingular("(^analy)ses$", "$1sis");
        AddSingular("([^f])ves$", "$1fe");
        AddSingular("(hive)s$", "$1");
        AddSingular("(tive)s$", "$1");
        AddSingular("([lr])ves$", "$1f");
        AddSingular("([^aeiouy]|qu)ies$", "$1y");
        AddSingular("(s)eries$", "$1eries");
        AddSingular("(m)ovies$", "$1ovie");
        AddSingular("(x|ch|ss|sh)es$", "$1");
        AddSingular("([m|l])ice$", "$1ouse");
        AddSingular("(bus)es$", "$1");
        AddSingular("(o)es$", "$1");
        AddSingular("(shoe)s$", "$1");
        AddSingular("(cris|ax|test)es$", "$1is");
        AddSingular("(octop|vir|alumn|fung)i$", "$1us");
        AddSingular("(alias|status)es$", "$1");
        AddSingular("^(ox)en", "$1");
        AddSingular("(vert|ind)ices$", "$1ex");
        AddSingular("(matr)ices$", "$1ix");
        AddSingular("(quiz)zes$", "$1");
        AddIrregular("person", "people");
        AddIrregular("man", "men");
        AddIrregular("child", "children");
        AddIrregular("sex", "sexes");
        AddIrregular("move", "moves");
        AddIrregular("goose", "geese");
        AddIrregular("alumna", "alumnae");
        AddUncountable("equipment");
        AddUncountable("information");
        AddUncountable("rice");
        AddUncountable("money");
        AddUncountable("species");
        AddUncountable("series");
        AddUncountable("fish");
        AddUncountable("sheep");
        AddUncountable("deer");
        AddUncountable("aircraft");
    }

    private static void AddIrregular(string singular, string plural)
    {
        var objArray = new object[5];
        objArray[0] = "(";
        objArray[1] = singular[0];
        objArray[2] = ")";
        objArray[3] = singular.Substring(1);
        objArray[4] = "$";
        AddPlural(string.Concat(objArray), string.Concat("$1", plural.Substring(1)));
        var objArray1 = new object[5];
        objArray1[0] = "(";
        objArray1[1] = plural[0];
        objArray1[2] = ")";
        objArray1[3] = plural.Substring(1);
        objArray1[4] = "$";
        AddSingular(string.Concat(objArray1), string.Concat("$1", singular.Substring(1)));
    }

    private static void AddPlural(string rule, string replacement)
    {
        _plurals.Add(new Rule(rule, replacement));
    }

    private static void AddSingular(string rule, string replacement)
    {
        _singulars.Add(new Rule(rule, replacement));
    }

    private static void AddUncountable(string word)
    {
        _uncountables.Add(word.ToLower());
    }

    private static string ApplyRules(List<Inflector.Rule> rules, string word)
    {
        string str = word;
        if (!_uncountables.Contains(word.ToLower()))
        {
            for (int i = rules.Count - 1; i >= 0; i--)
            {
                string str1 = rules[i].Apply(word);
                str = str1;
                if (str1 != null)
                {
                    break;
                }
            }
        }
        return str;
    }

    public static string Camelize(this string lowercaseAndUnderscoredWord)
    {
        return lowercaseAndUnderscoredWord.Pascalize().Uncapitalize();
    }

    public static string Capitalize(this string word)
    {
        return string.Concat(word.Substring(0, 1).ToUpper(), word.Substring(1).ToLower());
    }

    public static string Dasherize(this string underscoredWord)
    {
        return underscoredWord.Replace('\u005F', '-');
    }

    public static string Humanize(this string lowercaseAndUnderscoredWord)
    {
        return Regex.Replace(lowercaseAndUnderscoredWord, "_", " ").Capitalize();
    }

    private static string Ordanize(int number, string numberString)
    {
        int num = number % 100;
        if (num < 11 || num > 13)
        {
            int num1 = number % 10;
            switch (num1)
            {
                case 1:
                {
                    return string.Concat(numberString, "st");
                }
                case 2:
                {
                    return string.Concat(numberString, "nd");
                }
                case 3:
                {
                    return string.Concat(numberString, "rd");
                }
            }
            return string.Concat(numberString, "th");
        }
        else
        {
            return string.Concat(numberString, "th");
        }
    }

    public static string Ordinalize(this string numberString)
    {
        return Inflector.Ordanize(int.Parse(numberString), numberString);
    }

    public static string Ordinalize(this int number)
    {
        return Inflector.Ordanize(number, number.ToString());
    }

    public static string Pascalize(this string lowercaseAndUnderscoredWord)
    {
        string str = lowercaseAndUnderscoredWord;
        string str1 = "(?:^|_)(.)";
        return Regex.Replace(str, str1, (Match match) => match.Groups[1].Value.ToUpper());
    }

    public static string Pluralize(this string word)
    {
        return Inflector.ApplyRules(Inflector._plurals, word);
    }

    public static string Singularize(this string word)
    {
        return Inflector.ApplyRules(Inflector._singulars, word);
    }

    public static string Titleize(this string word)
    {
        string str = word.Underscore().Humanize();
        string str1 = "\\b([a-z])";
        return Regex.Replace(str, str1, (Match match) => match.Captures[0].Value.ToUpper());
    }

    public static string Uncapitalize(this string word)
    {
        return string.Concat(word.Substring(0, 1).ToLower(), word.Substring(1));
    }

    public static string Underscore(this string pascalCasedWord)
    {
        return Regex.Replace(Regex.Replace(Regex.Replace(pascalCasedWord, "([A-Z]+)([A-Z][a-z])", "$1_$2"), "([a-z\\d])([A-Z])", "$1_$2"), "[-\\s]", "_").ToLower();
    }

    private class Rule
    {
        private readonly Regex _regex;

        private readonly string _replacement;

        public Rule(string pattern, string replacement)
        {
            this._regex = new Regex(pattern, RegexOptions.IgnoreCase);
            this._replacement = replacement;
        }

        public string Apply(string word)
        {
            if (this._regex.IsMatch(word))
            {
                return this._regex.Replace(word, this._replacement);
            }
            else
            {
                return null;
            }
        }
    }
}