Friday, June 10, 2016

More Filter Testing

I recently wrote a post on how to test whether a filter was called from a controller.  Today I needed to test whether a filter was called from a factory, which is slightly different.  When we test controllers we instantiate the controller and inject what we want.  That means when we inject the $filter service we can just supply our own spy instead.  In a factory, though, we don't really instantiate the factory.  Instead the factory is just kinda there and we inject other factories and services into it.  (I'm sure there's a way to "instantiate" a factory for testing purposes and I know you don't actually "instantiate" things in JavaScript, but get over it.)  I needed a way to globally tell Angular that during testing I didn't want to use the normal $filter service when it came across it in my factory.  Fortunately, I also recently wrote a post on how to test the $mdSidenav service that's part of Angular Material. I put the two posts together and came up with a solution that works very well.

What I ultimately did was checked into the Angular source code for the $filter service (here) and found that it's pretty straightforward.  It just uses the $injector service to find the registered filter by name and return it.  So I mimicked that in my spy, except that I returned another spy when I came across the filter I wanted to test.  It sounds a bit confusing (even to me) writing it out so why don't you just check out the code below.  That should make more sense.

The code:
angular.module('app', []).factory('myFactory', function($filter) {
  var factory = {};

  factory.states = [
    { name: 'Alabama', id: 'AL' },
    { name: 'Alaskas', id: 'AK' },
    { name: 'Arizona', id: 'AZ' },
    { name: 'Arkansas', id: 'AR' }
  ];
  
  factory.sort = function() {
    return $filter('orderBy')(factory.states, 'id');
  };

  return factory;
});

The spec:
describe('test suite', function() {
  var myFactory, orderByFilterSpy, filterSpy;
  
  beforeEach(module('app'));
  
  beforeEach(module(function($provide, $injector){
    orderByFilterSpy = jasmine.createSpy('orderBy');
    filterSpy = jasmine.createSpy('$filter').and.callFake(function(name) {
      switch(name) {
        case 'orderBy':
          return orderByFilterSpy;
        default:
          return $injector.get(name + 'Filter');
      }
    });

    $provide.factory('$filter', function() {
      return filterSpy;
    });
  }));

  beforeEach(inject(function(_myFactory_) {
    myFactory = _myFactory_;
  });
  
  it('should call orderByFilter', function() {
    // arrange
    myFactory.states = [{id: 'AZ', name: 'Arizona'}, {id: 'AL', name: 'Alabama'}, {id: 'AK', name: 'Alaska'}, {id: 'AR', name: 'Arkansas'}];

    // act
    myFactory.sort();

    // assert
    expect(filterSpy).toHaveBeenCalledWith('orderBy');
    expect(orderByFilterSpy).toHaveBeenCalledWith([{id: 'AZ', name: 'Arizona'}, {id: 'AL', name: 'Alabama'}, {id: 'AK', name: 'Alaska'}, {id: 'AR', name: 'Arkansas'}], 'id');
  });
});

That's it!  As a little added bonus, any filters other than orderBy that happen to get called in my factory should get passed through (I haven't validated that part yet, but it looks like it would do that so I'm rolling with it).

Tuesday, June 7, 2016

Setting Up Angular Without .NET

I wrote a post a while back detailing how to get Angular JS working with an MVC project in ASP .NET.  If you've been keeping up with my posts (which, let's be honest, you haven't) you'll know that I'm now working purely on Angular.  This presented the challenge of standing up an Angular app without using ASP.NET.  Indeed the back-end of my project (it's technically the service tier, but from an Angular perspective it's the back-end) is entirely done in Java (which is one of the things that makes Angular so much fun: it doesn't care what the back-end is).

It turns out that using npm makes standing an Angular application up from scratch really easy.  If you don't have npm you're going to need it.  If you download and install node.js (from here) you'll have npm.  You may have to tweak your PATH variable to identify where npm is, but it could be good to go without doing anything.

Once you have node (and therefore npm) installed you'll want to setup your directory structure.  I'm going to keep mine basic for this example and follow John Papa's guidelines.  I'll have a root folder (C:\Dev\example) that contains an app folder.  The root folder will have index.html (the name will be important in a moment) and nothing else (for now).  The app folder will stay empty for now.  Open a command prompt and change directory to the root folder.  In my case I used cd c:\dev\example.  Now you can start running npm commands to get things going.

The first thing to do is run npm init. Running this command without any parameters will prompt you to answer some questions. You can go that route, or you can use the -f, --force, -y, or --yes parameters to use defaults.  Once you finish answering those questions you should have a package.json file in your root folder.  Go ahead and open it with notepad or something similar and take a look in it.  You should see your answers in the expected places in the file.  I'm not going to go into the file here, but you should be able to Google anything that confuses you.

Now that we have our package.json file we can start installing packages and getting ready to run our web server.  Run the command npm install lite-server --save-dev. This installs the lite-server package and saves a reference to that installation in package.json in the devDependencies section.  You should now see a new folder in your root called node_modules.  If you don't see it there you did something wrong.  Start over.  If you open the node_modules folder you should see another folder called lite-server.  Since node.js is entire JavaScript based, (I think) all npm packages are pure JavaScript files.  You can actually navigate your way through that folder structure and look at what makes it tick.  But we're not going to do that.  We're trying to get Angular setup and we've got a lot to do still.

Back in the command prompt run the command npm install angular --save-dev to install Angular and once again save it to package.json (that's what the --save-dev part does).  Now we have the Angular files we need, but we're not ready to use them yet.  If you look in package.json one more time you will see a funky syntax that is used for versioning.  In my case the version of my Angular node in devDependencies is "^1.5.6" which means (as far as I can tell) that later (and we'll get to it) if I re-install my dependencies and there is a version 1.5.7 that will be downloaded instead of 1.5.6.  If I want to make sure I always have 1.5.6 then I need to remove the caret.

We are technically done with npm packages for now, though we could add more if we wanted to.  Open package.json and this time we're going to edit it manually.  Find the "scripts" property.  If you don't have one, just create a property named "scripts" and set it to an empty object ({}).  Add a property to the "scripts" object called "start" and give it a value of "lite-server".  That creates a method for us to launch an instance of lite-server we downloaded earlier.

Open index.html and add some markup to it.  Include a script tag that looks like this:
<script src="node_modules/angular/angular.min.js"></script>
This is the reference to the Angular files we downloaded earlier. Now we're ready to define our own Angular scripts and call it a day.

In the markup, you'll need to use the ng-app directive to specify a name for your application. That name will match whatever you name your main module (later). So my html tag is going to look like this: <html ng-app="example">. If your lite-server is running still (which it should be so if it isn't run npm start again) your browser should automatically refresh to show your latest changes.

Create a new file in the app folder you created earlier and call it module.js.  Honestly, you can name it whatever you want, but my preference is module.js.  In that file create your Angular module.  My code looks like this:
angular.module('example', []);
Create another new file in the app folder and call it mainController.js. Again, you can call it whatever you want, but mainController.js works for this example. Here's my code:
angular.module('example').controller('mainController', function($scope) {
  $scope.message = 'And now we\'re finished';
});
The only thing left to do is reference our new script files and create some markup for the new controller. In index.html, include your two new script files:
<script src="app/module.js"></script>
<script src="app/mainController.js"></script>
and then modify your markup to include a new div that uses the ng-controller directive to bind your controller to it:
<div ng-controller="mainController">{{message}}</div>
When your page automatically refreshes you should see the message you set in your mainController. 

That's it!  You now have an Angular app that's ready to go.  You can build on it however you want and as long as lite-server is running every time you save changes to index.html your page will refresh.

BONUS: The reason we used the --save-dev parameter when we installed our dependencies was so that we can share our code with other developers really easily without including the (sometimes huge) node_modules folder.  If you zip up all the other files and folders except node_modules and give it to another developer, that person can simply run npm install to install all of the dependencies that are saved.  In this case that's only two, but it could easily be many, many more.

Friday, June 3, 2016

Testing $mdMedia

As I mentioned before I'm working with Angular Material on my project.  Today I had cause to use the $mdMedia service, which accepts a string parameter and returns either true or false based on the screen size.  For example, $mdMedia('lg') will return true if the screen is between 1280px and 1919px.  The service is great, but testing it was - once again - tricky.

I ended up using the same trick I used to test $mdSidenav, but modified it just a little bit.  In the interests of making it easier on myself next time I have to test $mdMedia, here we go.
var mediaMock, mediaQueryResult;
beforeEach(module(function ($provide) {
  mediaMock = jasmine.createSpy('$mdMedia');
  $provide.factory('$mdMedia', function() {
    return function() {
      return mediaQueryResult;
    };
  });
}));

it('should do something when $mdMedia() returns false regardless of what is passed to it', function() {
  // arrange
  mediaQueryResult = false;

  // act
  myFactory.myFunction();

  // assert
  expect(myFactory.myOtherFunction).toHaveBeenCalled();
});

it('should do something when $mdMedia() returns true regardless of what is passed to it', function() {
  // arrange
  mediaQueryResult = true;

  // act
  myFactory.myFunction();

  // assert
  expect(myFactory.myOtherFunction).not.toHaveBeenCalled();
});

That's all I had to do to get it to work.  I'm sure there's a way to vary the result based on the parameter, but I didn't need to do that so I didn't solve that problem... yet.