Friday, November 18, 2016

Testing Exception Messages with MS Test

I usually use NUnit to do my C# unit tests, but when I need to use the Microsoft Fakes framework I have to use the built-in (to Visual Studio) MS Test framework.  This is because Fakes requires instrumentation ability that NUnit just doesn't have.  It also means I can't use the Resharper test runner like I normally do.  Neither of those changes is usually a problem, but today I found out that testing an exception message in MS Test is actually a bit tricky.

You can use the
[ExpectedException]
attribute around a test to indicate to MS Test that you expect that test to throw an exception. However, even though there is an overload for that attribute that accepts a message, it's not actually used to test whether the thrown exception has the message you specify in the attribute. So
[ExpectedException(typeof(ArgumentNullException), "A name is required")]
doesn't actually check whether the message on the exception is
"A name is required"
. Instead, if that test fails (which would happen if an exception wasn't thrown since you're expecting that one will be thrown) the text that will appear in Test Explorer is
"A name is required"
. The good news is that there's an easy way around this. The bad news is you still have to create your own attribute to do it. It's not complicated. h/t to BlackjacketMack on SO for posting his solution here.

   1:  public class ExpectedExceptionWithMessageAttribute : ExpectedExceptionBaseAttribute
   2:  {
   3:      public Type ExceptionType { get; set; }
   4:   
   5:      public string ExpectedMessage { get; set; }
   6:   
   7:      public ExpectedExceptionWithMessageAttribute(Type exceptionType)
   8:      {
   9:          ExceptionType = exceptionType;
  10:      }
  11:   
  12:      public ExpectedExceptionWithMessageAttribute(Type exceptionType, string message)
  13:      {
  14:          ExceptionType = exceptionType;
  15:          ExpectedMessage = message;
  16:      }
  17:   
  18:      protected override void Verify(Exception exception)
  19:      {
  20:          if (exception.GetType() != ExceptionType)
  21:          {
  22:              NUnit.Framework.Assert.Fail(
  23:                  "ExpectedExceptionWithMessageAttribute failed. Expected exception type: {0}. Actual exception type: {1}. Exception message: {2}",
  24:                  ExceptionType.FullName, exception.GetType().FullName, exception.Message);
  25:          }
  26:   
  27:          var actualMessage = exception.Message.Trim();
  28:   
  29:          if (ExpectedMessage != null)
  30:          {
  31:              Assert.AreEqual(ExpectedMessage, actualMessage);
  32:          }
  33:      }
  34:  }

You use it just like you used
[ExpectedException]
earlier.
[ExpectedExceptionWithMessage(typeof(ArgumentNullException), "A name is required")]

More Fun with Shims

I'm finally back to doing some server side code (as opposed to the client stuff I've been working exclusively with for several months) and I found myself in need of some unit tests.  It's been a long time since I used a shim from Microsoft's Fakes framework so I had to poke and prod it for a while to work and I don't want to forget what I did.  Here goes:

In this particular example I was working with the WSUS API provided by Microsoft to interact with WSUS.  I specifically was trying to save a new signing certificate, but I obviously didn't want to actually do that on a WSUS server.  The solution was to use shims and stubs this time.

   1:  private string _fileNameParameter = "empty";
   2:  private SecureString _passwordParameter = new SecureString();

   1:  private StubIUpdateServer PrepareIUpdateServerStub()
   2:  {
   3:      FakesDelegates.Action<string, SecureString> setSigningCertificateAction = (fileName, password) =>
   4:      {
   5:          _fileNameParameter = fileName;
   6:          _passwordParameter = password;
   7:      };
   8:   
   9:      var iUpdateServerConfiguration = new StubIUpdateServerConfiguration
  10:      {
  11:          SetSigningCertificateStringSecureString = setSigningCertificateAction
  12:      };
  13:   
  14:      var iUpdateServerStub = new StubIUpdateServer
  15:      {
  16:          GetConfiguration = () => iUpdateServerConfiguration
  17:      };
  18:   
  19:      return iUpdateServerStub;
  20:  }

   1:  [TestMethod]
   2:  public void SetSigningCertificateShouldPassParametersToWsusApiAndReturnTrue()
   3:  {
   4:      using (ShimsContext.Create())
   5:      {
   6:          // arrange
   7:          const string expectedFileName = "fileName";
   8:          const string expectedPasswordString = "password";
   9:          var expectedPassword = new SecureString();
  10:          foreach (var c in expectedPasswordString)
  11:          {
  12:              expectedPassword.AppendChar(c);
  13:          }
  14:   
  15:          ShimAdminProxy.GetUpdateServerStringBooleanInt32 = (name, isSsl, port) => PrepareIUpdateServerStub();
  16:   
  17:          var wsusRepository = new WsusRepository();
  18:   
  19:          // act
  20:          var result = wsusRepository.SetSigningCertificate(expectedFileName, expectedPassword);
  21:   
  22:          // assert
  23:          Assert.AreEqual(expectedFileName, _fileNameParameter);
  24:          Assert.AreEqual(expectedPassword.ToString(), _passwordParameter.ToString());
  25:          Assert.IsTrue(result);
  26:      }
  27:  }

I broke out a bunch of the stub creation stuff so I could reuse it across multiple tests.  That's what the PrepareIUpdateServerStub method is for.  That's also why I have the global variables _fileNameParameter and _passwordParameter.  The method I was testing is essentially a pass-through to the WSUS API that we reuse in multiple applications so the method itself was fairly straightforward (check if a file and password were passed, then pass them on to WSUS).

Like I said at the beginning, working with Shims, Fakes, and Stubs is always a challenge for me so hopefully this documentation will help me next time I need to do it.

Tuesday, November 15, 2016

Local NPM Package

If you find yourself writing an NPM package, but you don't want to release it before you test it, there's a neat way to install a package from your local machine to another project.  You can use npm link instead of npm install.

Go to the directory containing the package you're writing (that you want to test by referencing in another project) and make sure to do an npm init on that directory (this creates the package.json file used by npm).  Once you're done run npm link.  Now go to the project you want to reference the package in and run npm link and pass it the name of the package you used during npm init.

That's it!

Thursday, October 20, 2016

TemplateUrl Function for Dynamic Templating

I've come across multiple situations while using Angular JS where I'd like to dynamically load a template into a directive.  It used to be pretty difficult, but in more recent versions (I'm not sure when it was introduced) you can pass a function to templateUrl to somewhat dynamically determine which template you want to use.  Most recently this came in handy when I was nesting a bunch of tables within each other (yes, it was a valid use case), but I also wanted those tables to be available alone.

I made a template for each table - 5 total - and then passed the relative url of the template I wanted as an attribute to the directive.  So it worked out like this:

<ea-children details="childrenFirst" template-url="/templates/first.html"></ea-children>
<ea-children details="childrenSecond" template-url="/templates/second.html"></ea-children>
<ea-children details="childrenThird" template-url="/templates/third.html"></ea-children>

As you can see I was able to reuse the same directive three times with potentially drastically different markup.  The templateUrl function looks like this:

templateUrl: function (element, attributes) {
    return attributes.templateUrl;
}

One major item to note is that you don't have access to scope variables because the template is requested before scope is initialized.

Hopefully next time I need to do this (assuming I'm still using Angular 1.x) I'll remember to check here first.  Happy coding!

Tuesday, October 11, 2016

Angular Tree View

For the second (or maybe third) time I faced a situation that would drastically benefit from a Tree View control.  Since I love Angular and was using it anyway, I finally set out to create a Tree View control I could reuse and be proud of.  It's pretty basic, but that's kinda the point.  There's actually a little bit of styling on this one, too.  Once again I'm going to try to get it up on Github at some point, but for now you can check it out on plnkr.

UPDATE: I've changed the directive significantly, checked it into Github and also created an npm package out of it called angular-tree-view.  I'm still updating a lot of it, but it's alive and in the wild now!

Check it out on Github
Check it out on npm

An important piece to note is that this feature actually uses two different directives nested within each other.  There's the parent item, which is a tree-view and then there's the child item, which is a tree-view-item.  Each tree-view-item can have another tree-view in it, which can then have another tree-view-item, which can then have another tree-view, etc.

Directive Requirements:
  • Each item can have an unlimited number of sub-items
  • Each sub-item can have an unlimited number of sub-items (ad infinitum)
  • The user can specify a callback to be invoked when a sub-item with no sub-items of its own is clicked
  • The selected item should be indicated by the application of an "active-branch" class
Directive Module Code:
angular.module('ea.treeview', []).directive('eaTreeView', function() {
  return {
        restrict: 'E',
        scope: {
            items: '=',
            isRoot: '@',
            callback: '&'
        },
        link: function(scope) {
            scope.callback = scope.callback();
        },
        controller: function ($scope) {
            this.isRoot = $scope.isRoot;
        },
        template: '<ea-tree-view-item item="item" callback="callback" data-ng-repeat="item in items"></ea-tree-view-item>'
    }
}).directive('eaTreeViewItem', function() {
  return {
        restrict: 'E',
        scope: {
            item: '=',
            callback: '&'
        },
        require: '^eaTreeView',
        link: function (scope, element, attributes, controller) {
          scope.callback = scope.callback();
            scope.activate = function () {
              var activeElements = document.getElementsByClassName('active-branch');
              angular.forEach(activeElements, function(activeElement){
                angular.element(activeElement).removeClass('active-branch');
              });
              element.children('div').addClass('active-branch');
              scope.callback(scope.item);
            };

            scope.hasChildren = function () {
                return !!scope.item.items && !!scope.item.items.length;
            };

            scope.hasParent = function() {
                return !controller.isRoot;
            };

            scope.toggleExpanded = function() {
                scope.item.expanded = !scope.item.expanded;
            };
        },
        templateUrl: 'treeViewItem.html'
    }
});

The treeViewItem Template:
<div>
    <div data-ng-if="hasChildren()" class="tree-parent" data-ng-class="{'tree-child': hasParent()}">
        <div data-ng-click="toggleExpanded()" class="clickable">
            <i class="fa" data-ng-class="{'fa-chevron-right': !item.expanded, 'fa-chevron-down': item.expanded}"></i>
            <span>{{item.display}}</span>
        </div>
        <ea-tree-view data-ng-if="item.expanded" callback="callback" items="item.items"></ea-tree-view>
    </div>
    <div data-ng-if="!hasChildren()" data-ng-click="activate()" class="tree-child clickable">{{item.display}}</div>
</div>

A dash of styling:
.clickable {
  cursor: pointer;
}

.active-branch {
    background-color: lightgrey;
}

.tree-parent {
  padding-left: 0;
}

.tree-child {
  padding-left: 1em;
}

.tree-final-child {
  padding-left: 2em;
}

And finally some usage:
<ea-tree-view items="model.items" is-root="true" callback="go"></ea-tree-view>

angular.module('demo', ['ea.treeview']).controller('mainController', function($scope) {
  $scope.model = {
    items: [
      {
        display: 'Abe',
        items: [
          {display: 'Homer', items: [{display: 'Bart'}, {display: 'Lisa'}, {display: 'Maggie'}]},
          {display: 'Herb'},
          {display: 'Abbie'}
        ]
      },
      {
        display: 'Jacqueline',
        items: [
          {display: 'Patty'},
          {display: 'Selma'},
          {display: 'Marge', items: [{display: 'Bart'}, {display: 'Lisa'}, {display: 'Maggie'}]}
        ]
      }
    ]
  };
  
  $scope.go = function (item) {
      console.log("You could have navigated!", item);
  };
});

Angular Tri-State Loading Directive

I had a situation where I wanted to load some data asynchronously and display a visual cue to the user that the action had been completed.  However, there was a possibility that more than one result could be returned and the user would have to take some action to confirm which item they want to use.  I also wanted to show the user a "loading" message so they'd have the visual cue that something was happening.  Since I was already using Angular and Font Awesome I decided the best way to go about it would be to use a directive.  I prefer my directives to be configurable as much as possible so I can reuse them in the future with relative ease (that is the point of directives after all).  This is what I came up with.  I will at some point get it uploaded to Github, but for now this is the code.  (You can check it out on plnkr, too).

First I had to create the directive.  I've refactored it a little bit so that it's usable as a separate module by just including 'ea.loading' in your module import list when you declare your module.

Directive Requirements:

  • User can configure which icons to use, but defaults will be used if nothing is specified
  • User can configure which color(s) to use on which states and no defaults will be used
  • User can specify a callback function and parameters for each of the three states separately (for click events only)
  • User can specify an initial state, but a default of 0 (do not show) will be used if nothing is specified

Directive Module Code:
angular.module('ea.loading', []).directive('eaTriStateLoading', function() {
  return {
    restrict: 'E',
    scope: {
      animate: '@',
      initialValue: '@',
      loadingCallback: '&',
      loadingCallbackParameters: '=',
      loadingClass: '@',
      loadingColor: '@',
      notOkCallback: '&',
      notOkClass: '@',
      notOkColor: '@',
      okCallback: '&',
      okClass: '@',
      okColor: '@',
      state: '='
    },
    link: {
      pre: function(scope, element, attributes) {
        scope.animate = scope.animate || true;
        scope.state = parseInt(scope.initialValue) || 0;
        scope.loadingClass = scope.loadingClass || 'fa-spinner';
        scope.okClass = scope.OkClass || 'fa-check';
        scope.notOkClass = scope.notOkClass || 'fa-exclamation-triangle';
      },
      post: function(scope, element, attributes) {
        function buildParameterObject(parameters) {
          var result = {};
          
          for (var i = parameters.length; --i >= 0;) {
            var parameter = parameters[i];
            for (var j = Object.keys(parameter).length; --j >= 0;) {
              // key is the name of the parameter
              // parameter[key] is the name of the property on scope that will contain the value to be passed to the parameter
              var key = Object.keys(parameter)[j];
              var value = scope.$parent[parameter[key]] || parameter[key].substring(1, parameter[key].length - 1);
              result[key] = value;
            }
          }
          
          return result;
        }
        
        scope.click = function($event) {
          var parameter = {};
          if (scope.state === 1 && !!scope.loadingCallback && angular.isDefined(scope.loadingCallback) && angular.isFunction(scope.loadingCallback)) {
            if (!!scope.loadingCallbackParameters) {
              parameter = buildParameterObject(scope.loadingCallbackParameters);
            }
            parameter.$event = $event;
            scope.loadingCallback(parameter);
          } else if (scope.state === 2 && angular.isDefined(scope.okCallback) && angular.isFunction(scope.okCallback)) {
            if (!!scope.okCallbackParameters) {
              parameter = buildParameterObject(scope.okCallbackParameters);
            }
            parameter.$event = $event;
          } else if (scope.state === 3 && angular.isDefined(scope.noOkCallback) && angular.isFunction(scope.noOkCallback)) {
            if (!!scope.notOkCallbackParameters) {
              parameter = buildParameterObject(scope.notOkCallbackParameters);
            }
            parameter.$event = $event;
            scope.notOkCallback(parameter);
          }
        };
      },
    },
    template: '<i class="fa" ng-class="{\'{{loadingClass}}\': state === 1, \'fa-pulse\': state === 1 && animate === true, \'{{okClass}}\': state === 2, \'{{notOkClass}}\': state === 3}" ng-click="click($event)" ng-style="{color: (state === 1 ? loadingColor : state === 2 ? okColor : notOkColor)}"></i>'
  }
});

And some use cases:
<ea-tri-state-loading loading-callback-parameters="model.firstDemoLoadingCallbackParameters" loading-callback="clickCallback(property, model.age, $event)" loading-color="blue" not-ok-color="red" ok-callback="decrement()" ok-color="green" state="model.firstDemoValue"></ea-tri-state-loading>
<ea-tri-state-loading loading-class="fa-circle-o-notch" loading-callback-parameters="[{'property': '\'secondDemoValue\''}]" loading-callback="clickCallback(property, $event)" loading-color="purple" not-ok-color="yellow" ok-color="orange" initial-value="2" state="model.secondDemoValue"></ea-tri-state-loading>
<ea-tri-state-loading animate="false" ok-class="fa-rocket" state="model.thirdDemoValue"></ea-tri-state-loading>
<ea-tri-state-loading not-ok-class="fa-remove" state="model.fourthDemoValue"></ea-tri-state-loading>
<ea-tri-state-loading state="model.fifthDemoValue"></ea-tri-state-loading>
And finally the controller that would back those use cases:
angular.module('demo', ['ea.loading']).controller('mainController', function($scope) {
  $scope.states = {
    first: 1,
    second: 2,
    third: 3
  };
  $scope.model = {
    firstDemoValue: 1,
    secondDemoValue: 1,
    thirdDemoValue: 1,
    fourthDemoValue: 2,
    fifthDemoValue: 3,
    age: 12,
    firstDemoLoadingCallbackParameters: [
      {'property': '"firstDemoValue"'},
      {'value': 'age'}
    ]
  };
  
  $scope.clickCallback = function(property, value, $event) {
    console.log('property', property);
    console.log('value', value);
    console.log('$event', $event);
  };
  
  $scope.simulateLoading = function(property) {
    $scope.model[property] = 1;
  }
  
  $scope.simulateOk = function(property) {
    $scope.model[property] = 2;
  };
  
  $scope.simulateNotOk = function(property) {
    $scope.model[property] = 3;
  };
});

Wednesday, September 14, 2016

Order of Import

Hi! It's been a while and I know you missed me.  Awwww, you're sweet.  I missed you, too.

Anyway, I had an issue this morning that I wanted to document in case it happens again. I'm setting up unit testing on a new project with Karma and Jasmine and I was getting a weird error for a while.  Specifically, I was getting "TypeError: Attempted to assign to readonly property" and it was driving me crazy (my wife would tell you that's quite a short drive).  Eventually I figured out that my scripts were being referenced in the wrong order in my Karma config file.  Because of alphabetization I was importing angular-mocks.js before angular.min.js (apparently hyphens come before periods).  I switched them around and voila! Now I'm all set again.

Bonus: I was also running into several weird errors with angular-mocks until I went back and grabbed an older version. Since this project is using AngularJS 1.3.5 I went back to angular-mocks 1.3.19 and now everything works as expected.