Updates:
-- 02/28/2016: Updated to the latest versions of NodeJS, ExpressJS, MongoDB, and AngularJS; added a section on persistant logins.
-
Keep in mind that this solution posed in this tutorial is not the only solution to the question at hand, and it may not even be the right solution for your situation. Regardless of the solution you implement, it is important to note that since end users have full control of the browser as well as access to the front-end code, sensitive data living in your server-side API must be secure. In other words, make certain that you implement an authentication strategy on the server-side to protect sensitive API endpoints.
+
Objectives
-
That said, we need to enable the following workflow:
+
By the end of this tutorial, you will be able to…
-
When the client accesses the main route, an index page is served, at which point Angular takes over.
-
The Angular app immediately “asks” the server if a user is logged in.
-
Assuming the server indicates that a user is not logged in, the client is immediately asked to log in.
-
Once logged in, the Angular app then tracks the user’s login status.
+
Discuss the benefits of using JWTs versus sessions and cookies
+
Discuss the overall client/server authentication workflow
+
Implement user authentication using JWTs with Angular
First, grab the boilerplate code from the project repo, install the requirements, and then test out the app:
-
-
$ npm start
-
+
Review
-
Navigate to http://localhost:3000/ and you should see a simple welcome message - “Yo!”. Once you’re finishing admiring the page, kill the server, and glance over the code within the project folder:
+
Before beginning, review the Introduction from Token-Based Authentication With Node so you have a solid understanding of what JWTs are and why you would want to use tokens over sessions for auth.
Make sure you can describe what’s happening on the server-side as well. Review the code from the node-token-auth repo, if necessary.
-
Nothing too spectacular. You can see that the back-end code resides in the “server” folder, while the front-end code lives in the “client” folder. Explore the files and folders within each.
+
With that, here’s the full user auth process:
-
Login API
+
+
Client logs in and the credentials are sent to the server
+
Server generates a token (if the credentials are correct)
+
Client receives and stores the token in local storage
+
Client then sends token to server on subsequent requests within the request header
+
-
Let’s start with the back-end API. This is already built out, for your convenience. Why? The focus of this tutorial is mainly on the client-side. If you’re looking for a back-end tutorial for setting up Passport with NodeJS, ExpressJS, and MongoDB take a look at this tutorial.
-
User Registration
+
Project Setup
-
Open the “routes” folder and locate the following code:
Here, we grab the values from the payload sent with the POST request (from the client-side), create a new User instance, and then attempt to add the instance to the database. If this succeeds a user is added, of course, and then we return a JSON response with a status of “success”. If it fails, an “error” response is sent.
-
-
Let’s test this via curl. Fire up the server, and then run the following command:
+
Install the dependencies, and then fire up the app by running gulp to make sure all is well. Navigate to http://localhost:8888 in your browser and you should see:
Try it again, with the exact same username and password, and you should see an error:
-
-
{
- "err": {
- "name": "UserExistsError",
- "message": "A user with the given username is already registered"
- }
-}
-
+
All of the client-side code lives in the “src” folder and the Angular app can be found in the “js” folder. Make sure you understand the app structure before moving on.
Before diving in, remember that since end users have full access to the power of the browser as well as DevTools and the client-side code, it’s vital that you not only restrict access to sensitive endpoints on the server-side - but that you also do not store sensitive data on the client-side. Keep this in mind as you add auth functionality to your own MEAN application stack.
-
Client-side Routing
-
Let’s add the remainder of the client-side routes to the main.js file:
+Next, add the script tag to *index.html*, just before the closing body tag:
-
Here, we created five new routes. Before we add the subsequent templates and controllers, let’s create a service to handle authentication.
+
-
Authentication Service
-
Start by adding the basic structure of the service to a new file called services.js in the “client” directory:
+
-
angular.module('myApp').factory('AuthService',
- ['$q', '$timeout', '$http',
- function ($q, $timeout, $http) {
- // create user variable
- var user = null;
+
+Add a new route handler to the *config.js* file:
- // return available functions for use in the controllers
- return ({
- isLoggedIn: isLoggedIn,
- getUserStatus: getUserStatus,
- login: login,
- logout: logout,
- register: register
- });
-}]);
-
Here, we simply defined the service name, AuthService, and then injected the dependencies that we will be using - $q, $timeout, $http - and then returned the functions, which we still need to write, for use outside the service.
-
-
Make sure to add the script to the index.html file:
</p>
-<script src="./services.js"></script>
+<p>Run gulp, and then navigate to <a href="http://localhost:8888/#!/login.">http://localhost:8888/#!/login.</a> If all went well you should see the <code>just a test</code> text.</p>
+<h2>Service</h2>
-<p>
-
-
-
Let’s create each function…
+<p>Next, let’s create a global service to handle a user logging in, logging out, and signing up. Add a new file called <em>services.js</em> to the “js” directory:</p>
+
+<pre><code class="javascript">(function() {
+
+ 'use strict';
+
+ angular
+ .module('tokenAuthApp.services', [])
+ .service('authService', authService);
+
+ authService.$inject = [];
+
+ function authService() {
+ /*jshint validthis: true */
+ this.test = function() {
+ return 'working';
+ };
+ }
+
+})();
+</code></pre>
+
+<p>Make sure to add it to the dependencies in <em>app.js</em>:</p>
+
+<pre><code class="javascript">angular
+ .module('tokenAuthApp', [
+ 'ngRoute',
+ 'tokenAuthApp.config',
+ 'tokenAuthApp.components.main',
+ 'tokenAuthApp.components.auth',
+ 'tokenAuthApp.services'
+ ]);
+</code></pre>
+
+<p>Add the script to the <em>index.html</em> file, below the config script:</p>
+
+<p>```html</p>
+
+<script type="text/javascript" src="./js/services.js"></script>
+
+
+<pre><code>
+### Sanity Check
+
+Before adding code to `authService()`, let's make sure the service itself is wired up correctly. To do that, within *auth.controller.js* inject the service and call the `test()` method:
+</code></pre>
+
+<p>authLoginController.$inject = [‘authService’];</p>
+
+<p>function authLoginController(authService) {
+ /<em>jshint validthis: true </em>/
+ const vm = this;
+ vm.test = ‘just a test’;
+ console.log(authService.test());
+}
Here, we used the $q service to set up a promise, which we’ll access in a future controller. We also utilized the $http service to send an AJAX request to the /user/login endpoint that we already set up in our back-end Node/Express app.
+
To test this we need to set up a back end…
-
Based on the returned response, we either resolve or reject and set the value of user to true or false, respectively.
Here, we followed the same formula as the login() function, except we sent a GET request rather than a POST and to be safe we just went ahead and handled the error the same as the success.
XMLHttpRequest cannot load http://localhost:3000/auth/register. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8888' is therefore not allowed access.
+
-
function register(username, password) {
+
This is a CORS issue. To fix, we need to update the server. Add the following code to src/server/config/main-config.js, just above app.use(cookieParser());:
- // create a new instance of deferred
- var deferred = $q.defer();
+
Again, we followed a similar formula to the logout() function. Can you tell what’s happening?
-
-
That’s it for the service. Keep in mind that we still have not “used” this service. In order to do that we just need to inject it into the necessary components in the Angular app. In our case, that will be the controllers, which we’ll build next.
+
Auth Login
+
+
Update auth.login.view.html:
+
+
```html
+
+
+
+
Login
+
+
+
+
-
Templates and Controllers
-
Looking back at our routes, we need to setup two partials/templates and three controllers:
+
+Take note of the form. We used the `ng-model` directive on each of the form inputs to capture those values in the controller. Also, when the form is submitted, the `ng-submit` directive handles the event by firing the `onLogin()` function.
-
</p>
-<divclass="col-md-4">
-<h1>Login</h1>
-<divng-show="error"class="alert alert-danger">{{errorMessage}}</div>
-<formclass="form"ng-submit="login()">
-<divclass="form-group">
-<label>Username</label>
-<inputtype="text"class="form-control"name="username"ng-model="loginForm.username"required>
-</div>
-<divclass="form-group">
-<label>Password</label>
-<inputtype="password"class="form-control"name="password"ng-model="loginForm.password"required>
-</div>
-<div>
-<buttontype="submit"class="btn btn-default"ng-disabled="disabled">Login</button>
-</div>
-</form>
-</div>
+<p>So, when the form is submitted, we capture the username and password and pass them to the <code>login()</code> method on the service.</p>
+<p>Test this out.</p>
-<p>
-
-
-
Add this file to the “partials” directory.
-
-
Take note of the form. We used the ng-model directive on each of the inputs so that we can capture those values in the controller. Also, when the form is submitted, the ng-submit directive handles the event by firing the login() function.
-
-
Next, within the “client” folder, add a new file called controllers.js. Yes, this will hold all of our Angular app’s controllers. Don’t forget to add the script to the index.html file:
-
-
1
-2
-3
-4
-5
-6
-
</p>
+<h2>Auth Register</h2>
+
+<p>Just like the login, we need to add a view and controller for registering a user. Start by adding the view, <em>auth.register.view.html</em>, to the “auth” folder:</p>
+
+<p>```html</p>
+
+<div class="row">
+ <div class="col-md-4">
+ <h1>Register</h1>
+ <hr><br>
+ <form ng-submit="authRegisterCtrl.onRegister()" novalidate>
+ <div class="form-group">
+ <label for="username">Username</label>
+ <input type="text" class="form-control" id="username" placeholder="enter username" ng-model="authRegisterCtrl.user.username" required>
+ </div>
+ <div class="form-group">
+ <label for="passwowrd">Password</label>
+ <input type="password" class="form-control" id="passwowrd" placeholder="enter password" ng-model="authRegisterCtrl.user.password" required>
+ </div>
+ <button type="submit" class="btn btn-default">Submit</button>
+ </form>
+ </div>
+</div>
-<script src="./controllers.js"></script>
+<pre><code>
+Add a new controller to *auth.controller.js*:
+</code></pre>
-<p>
-
Next, let’s add the token to localStorage for persistence by replacing the console.log(user.data); with localStorage.setItem('token', user.data.token);:
So, when the login() function is fired, we set some initial values and then call login() from the AuthService, passing the user inputed email and password as arguments. The subsequent success or error is then handled and the DOM/view/template is updated appropriately.
+
As long as that token is present, the user can be considered logged in. And, when a user needs to make an AJAX request, that token can be used.
-
Ready to test the first round-trip - client => server => client?
+
NOTE: Besides the token, you could also add the user id and username. You would just need to update the server-side to send back that info.
-
Fire up the server and navigate to http://localhost:3000/#/login in your browser. First, try logging in with the user credentials used to register earlier - e.g, test@test.com and test, respectively. If all went well, you should be redirected to the main URL. Next, try to log in using invalid credentials. You should see the error message flash, “Invalid username and/or password”.
+
Test this out. Ensure that the token is present in localStorage.
-
Logout
+
User Status
-
Add the controller:
+
To test out login persistence, we can add a new view that verifies that the user is logged in and that the token is valid.
- // call logout from service
- AuthService.logout()
- .then(function () {
- $location.path('/login');
- });
+
Take note of Authorization: 'Bearer ' + token. This is called a Bearer schema, which is sent along with the request. On the server, we are simply checking for the Authorization header, and then whether the token is valid. Can you find this code on the server-side?
- };
+
Then add a new file called auth.status.view.html to the “auth” folder:
-}]);
-
+
```html
-
Here, we called AuthService.logout() and then redirected the user to the /login route after the promise is resolved.
You’ve seen this before, so let’s move right on to testing.
-
-
Fire up the server and register a new user at http://localhost:3000/#/register. Make sure to test logging in with that new user as well.
-
-
Well, that’s it for the templates and controllers. We now need to add in functionality to check if a user is logged in on each and every change of route.
The $routeChangeStart event fires before the actual route change occurs. So, whenever a route is accessed, before the view is served, we ensure that the user is logged in. Test this out!
-
-
Route Restriction
-
-
Right now all client-side routes require a user to be logged in. What if you want certain routes restricted and other routes open?
-
-
You can add the following code to each route handler, replacing true with false for routes that you do not want to restrict:
The user is logged out, right? Why? Because the controller and services are called again, setting the user variable to null. This is a problem since the user is still logged in on the server side.
-
-
Fortunately, the fix is simple: Within the $routeChangeStart we need to ALWAYS check if a user is logged in. Right now, it’s checking whether isLoggedIn() is false. Let’s update getUserStatus() so that it checks the user status on the back-end:
That’s it. One thing you should note is that the Angular app can be used with various frameworks as long as the endpoints are set up correctly in the AJAX requests. So, you can easily take the Angular portion and add it to your Django or Pyramid or NodeJS app. Try it!
-
-
Check out a Python/Flask app with Angular Auth here
-]]>
-
-
-
-
-
- 2015-04-26T08:06:00-06:00
- http://mherman.org/blog/2015/04/26/testing-angularjs-with-protractor-and-karma-part-2
- This article details how to test a simple AngularJS application using unit tests and end-to-end (E2E) tests.
-
-
-
-
-
-
-
-
-
-
Part 1 - In the first part we’ll look at unit tests, which ensure that small, isolated pieces of code (e.g., a unit) behave as expected.
-
Part 2 - In part two we’ll address E2E tests, which verify that all the pieces of code (units) fit together by simulating the user experience through browser automation. (current)
-
-
-
-
Updates: December 3rd, 2016 - bumped dependencies
-
-
Having finished up unit testing, let’s now turn our attention to e2e testing using Protractor, which is a testing framework built specifically for AngularJS apps. Essentially, it runs tests against an app in the browser via Selenium Webdriver, interacting with the app from an end user’s perspective.
-
-
-
-
-
-
-
-
-
Since e2e tests are much more expensive than unit tests - e.g., they generally take more time to run and are harder to write and maintain - you should almost always focus the majority of your testing efforts on unit tests. It’s good to follow the 80/20 rule - 80% of your tests are unit tests, while 20% are e2e tests. That said, this tutorial series breaks this rule since the goal is to educate. Keep this in mind as you write your own tests against your own application.
-
-
Also, make sure you test the most important aspects/functions of your application with your e2e tests. Don’t waste time on the trivial. Again, they are expensive, so make each one count.
Assuming you followed the first part of this tutorial, checkout the third tag, v3, and then run the current test suite starting with the unit tests:
-
-
$ git checkout tags/v3
-$ gulp unit
-
-[23:30:01] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
-[23:30:01] Starting 'unit'...
-INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
-INFO [launcher]: Starting browser Chrome
-INFO [Chrome 42.0.2311 (Mac OS X 10.10.2)]: Connected on socket i04LmGbgt7P1lNIUTgIJ with id 48442826
-Chrome 42.0.2311 (Mac OS X 10.10.2): Executed 12 of 12 SUCCESS (0.236 secs / 0.051 secs)
-[23:30:06] Finished 'unit' after 4.43 s
-
-
-
For the e2e tests, you’ll need to open two new terminal windows. In the first new window, run webdriver-manager start. In the second, navigate to your project directory and then run the app - gulp.
-
-
Finally, back in the original window, run the tests:
-
-
$ gulp e2e
-
-[23:31:11] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
-[23:31:11] Starting 'e2e'...
-Using the selenium server at http://localhost:4444/wd/hub
-[launcher] Running 1 instances of WebDriver
-.
-
-Finished in 1.174 seconds
-1 test, 1 assertion, 0 failures
-
-[launcher] 0 instance(s) of WebDriver still running
-[launcher] chrome #1 passed
-
-
-
Everything look good?
-
-
The Tests
-
-
Open the test spec, spec.js, within the “tests/e2e” directory. Let’s look at the first test:
-
-
describe('myController', function () {
-
- it('the dom initially has a greeting', function () {
- browser.get('http://localhost:8888/#/one');
- expect(element(by.id('greeting')).getText()).toEqual('Hello, World!');
- });
-
-});
-
-
-
Notice how we’re still using Mocha and Chai to manage/structure the test so that it simply opens http://localhost:8888/#/one and then asserts that the text within the HTML element with an ID of greeting is Hello, World!. Simple, right?
-
-
Let’s take a quick look at the Angular services that we’re using:
Finally, one important thing to note is how these tests run. Notice that there’s no callbacks and/or promises in the test. How does that work with asynchronous code? Simple: Protractor continues to check each assertion until it passes or a certain amount of time passes. There also is a promise attached to most methods that can be access using then.
-
-
With that, let’s write some tests on our own.
-
-
TestOneController
-
-
Just like in the first part, open the controller code:
Looking at the Angular code along with the HTML, we know that on the button click, greeting is updated with the user supplied text from the input box. Sound right? Test this out: With the app running via Gulp, navigate to http://localhost:8888/#/one and manually test the app to ensure that the controller is working as it should.
-
-
Now since we already tested the initial state of greeting, let’s write the test to ensure that the state updates on the button click:
-
-
describe('TestOneController', function () {
-
- beforeEach(function() {
- browser.get('http://localhost:8888/#/one');
- });
-
- it('initially has a greeting', function () {
- expect(element(by.id('greeting')).getText()).toEqual('Hello, World!');
- });
-
- it('clicking the button changes the greeting if text is inputed', function () {
- element(by.css('[ng-model="newText"]')).sendKeys('Hi!');
- element(by.css('.btn-default')).click();
- expect(element(by.id('greeting')).getText()).toEqual('Hi!');
- });
-
- it('clicking the button does not change the greeting if text is not inputed', function () {
- element(by.css('.btn-default')).click();
- expect(element(by.id('greeting')).getText()).toEqual('Hello, World!');
- });
-
-});
-
-
-
So, in both new test cases we’re targeting the input form - via the global element function - and adding text to it with the sendKeys() method - Hi! in the first test and no text in the second. Then after clicking the button, we’re asserting that the text contained within the HTML element with an id of “greeting” is as expected.
-
-
Run the tests. If all went well, you should see:
-
-
[06:15:45] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
-[06:15:45] Starting 'e2e'...
-Using the selenium server at http://localhost:4444/wd/hub
-[launcher] Running 1 instances of WebDriver
-...
-
-Finished in 3.606 seconds
-3 tests, 3 assertions, 0 failures
-
-[launcher] 0 instance(s) of WebDriver still running
-[launcher] chrome #1 passed
-Michaels-MacBook-Pro-3:angular-testing-tutorial michael$
-
-
-
Did you see Chrome open in a new window and run the tests, then close itself? It’s super fast!! Want to run the tests in Firefox (or a different browser) as well? Simply update the Protractor config file, protractor.conf.js, like so:
Test it again. You should now see the tests run in both Chrome and Firefox simultaneously. Nice.
-
-
Finally, to simplify the code and speed up the tests (so we only search the DOM once per element), we can assign each element to a variable:
-
-
describe('TestOneController', function () {
-
- var greeting = element(by.id('greeting'));
- var textInputBox = element(by.css('[ng-model="newText"]'));
- var changeGreetingButton = element(by.css('.btn-default'));
-
- beforeEach(function() {
- browser.get('http://localhost:8888/#/one');
- });
-
- it('initially has a greeting', function () {
- expect(greeting.getText()).toEqual('Hello, World!');
- });
-
- it('clicking the button changes the greeting if text is inputed', function () {
- textInputBox.sendKeys('Hi!');
- changeGreetingButton.click();
- expect(greeting.getText()).toEqual('Hi!');
- });
-
- it('clicking the button does not change the greeting if text is not inputed', function () {
- textInputBox.sendKeys('');
- changeGreetingButton.click();
- expect(greeting.getText()).toEqual('Hello, World!');
- });
-
-});
-
-
-
Test one last time to ensure that this refactor didn’t break anything.
1
+<p>Test this out at <a href="http://localhost:8888/#!/status:">http://localhost:8888/#!/status:</a></p>
+
+<ul>
+<li>If there is a token in localStorage, you should see - <code>Logged In? true</code></li>
+<li>Otherwise, you should see <code>Logged In? false</code></li>
+</ul>
+
+
+<p>Finally, let’s redirect to the status page after a user successfully registers or logs in. Update the controllers like so:</p>
+
+<pre><code class="javascript">authLoginController.$inject = ['$location', 'authService'];
+authRegisterController.$inject = ['$location', 'authService'];
+authStatusController.$inject = ['authService'];
+
+function authLoginController($location, authService) {
+ /*jshint validthis: true */
+ const vm = this;
+ vm.user = {};
+ vm.onLogin = function() {
+ authService.login(vm.user)
+ .then((user) => {
+ localStorage.setItem('token', user.data.token);
+ $location.path('/status');
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ };
+}
+
+function authRegisterController($location, authService) {
+ /*jshint validthis: true */
+ const vm = this;
+ vm.user = {};
+ vm.onRegister = function() {
+ authService.register(vm.user)
+ .then((user) => {
+ localStorage.setItem('token', user.data.token);
+ $location.path('/status');
+ })
+ .catch((err) => {
+ console.log(err);
+ });
+ };
+}
+</code></pre>
+
+<p>Test it out!</p>
+
+<h2>Route Restriction</h2>
+
+<p>Right now, all routes are open; so, regardless of whether a user is logged in or not they, they can access each route. Certain routes should be restricted if a user is not logged in, while other routes should be restricted if a user is logged in:</p>
+
+<ol>
+<li><code>/</code> - no restrictions</li>
+<li><code>/login</code> - restricted when logged in</li>
+<li><code>/register</code> - restricted when logged in</li>
+<li><code>status</code> - restricted when not logged in</li>
+</ol>
+
+
+<p>To achieve this, add the following property to each route, replacing <code>false</code> with <code>true</code> for routes that you want to restrict:</p>
+
+<pre><code class="javascript">restrictions: {
+ ensureAuthenticated: false,
+ loginRedirect: false
+}
+</code></pre>
+
+<p>For example:</p>
+
+<pre><code class="javascript">function appConfig($routeProvider) {
+ $routeProvider
+ .when('/', {
+ templateUrl: 'js/components/main/main.view.html',
+ controller: 'mainController',
+ restrictions: {
+ ensureAuthenticated: false,
+ loginRedirect: false
+ }
+ })
+ .when('/login', {
+ templateUrl: 'js/components/auth/auth.login.view.html',
+ controller: 'authLoginController',
+ controllerAs: 'authLoginCtrl',
+ restrictions: {
+ ensureAuthenticated: false,
+ loginRedirect: true
+ }
+ })
+ .when('/register', {
+ templateUrl: 'js/components/auth/auth.register.view.html',
+ controller: 'authRegisterController',
+ controllerAs: 'authRegisterCtrl',
+ restrictions: {
+ ensureAuthenticated: false,
+ loginRedirect: true
+ }
+ })
+ .when('/status', {
+ templateUrl: 'js/components/auth/auth.status.view.html',
+ controller: 'authStatusController',
+ controllerAs: 'authStatusCtrl',
+ restrictions: {
+ ensureAuthenticated: true,
+ loginRedirect: false
+ }
+ })
+ .otherwise({
+ redirectTo: '/'
+ });
+}
+</code></pre>
+
+<p>Next, add the following function below the route handlers in <em>config.js</em>:</p>
+
+<pre><code class="javascript">function routeStart($rootScope, $location, $route) {
+ $rootScope.$on('$routeChangeStart', (event, next, current) => {
+ if (next.restrictions.ensureAuthenticated) {
+ if (!localStorage.getItem('token')) {
+ $location.path('/login');
+ }
+ }
+ if (next.restrictions.loginRedirect) {
+ if (localStorage.getItem('token')) {
+ $location.path('/status');
+ }
+ }
+ });
+}
+</code></pre>
+
+<p>The <code>$routeChangeStart</code> event fires before the actual route change occurs. So, whenever a route is accessed, we check the <code>restrictions</code> property:</p>
+
+<ol>
+<li>If <code>ensureAuthenticated</code> is true and there is no token present, then we redirect them to the login page</li>
+<li>If <code>loginRedirect</code> is true and there’s a token present, then we redirect them to the status page</li>
+</ol>
+
+
+<p>Simple, right?</p>
+
+<p>Update:</p>
+
+<pre><code class="javascript">angular
+ .module('tokenAuthApp.config', [])
+ .config(appConfig)
+ .run(routeStart);
+</code></pre>
+
+<p>Then test one last time.</p>
+
+<h2>What’s Next?</h2>
+
+<p>You’ve reached the end. Now what?</p>
+
+<ol>
+<li>You should handle those errors in each <code>.catch</code>.</li>
+<li>Check out <a href="https://github.com/sahat/satellizer">Satellizer</a>. It’s a nice token-based auth module for Angular. You can find sample code in the following repos - <a href="https://github.com/mjhea0/mean-token-auth">mean-token-auth</a> and <a href="https://github.com/mjhea0/mean-social-token-auth">mean-social-token-auth</a>.</li>
+<li>Try using this app with a different back-end. Since this app is just the client, you can literally use any language/framework to write a RESTful API in.</li>
+</ol>
+
+
+<p>Grab the final code from the <a href="https://github.com/mjhea0/angular-token-auth">angular-token-auth</a> repo. Comment below. Cheers!</p>
+]]></content>
+ </entry>
+
+ <entry>
+ <title type="html"><![CDATA[Handling User Authentication With the MEAN Stack]]></title>
+ <link href="http://mherman.org/blog/2015/07/02/handling-user-authentication-with-the-mean-stack/"/>
+ <updated>2015-07-02T07:04:00-06:00</updated>
+ <id>http://mherman.org/blog/2015/07/02/handling-user-authentication-with-the-mean-stack</id>
+ <content type="html"><![CDATA[<div style="text-align:center;">
+ <img src="http://mherman.org/images/mean-auth.png" style="max-width: 100%; border:0;" alt="mean stack authentication">
+</div>
+
+
+<p><br></p>
+
+<p><strong>This post provides a solution to the question, “How do I handle user authentication with the MEAN Stack - MongoDB, ExpressJS, AngularJS, and NodeJS?”.</strong></p>
+
+<blockquote><p>Much of this post is ported from <a href="https://realpython.com/blog/python/handling-user-authentication-with-angular-and-flask/">Handling User Authentication with Angular and Flask</a> from <a href="https://realpython.com">Real Python</a>.</p></blockquote>
+
+<p><em>Updates:</em>
+- 02/28/2016: Updated to the latest versions of NodeJS, ExpressJS, MongoDB, and AngularJS; added a section on persistant logins.</p>
+
+<p>Keep in mind that this solution posed in this tutorial is not the <em>only</em> solution to the question at hand, and it may not even be the <em>right</em> solution for your situation. Regardless of the solution you implement, it is important to note that since end users have full control of the browser as well as access to the front-end code, sensitive data living in your server-side API must be secure. <em>In other words, make certain that you implement an authentication strategy on the server-side to protect sensitive API endpoints.</em></p>
+
+<p>That said, we need to enable the following workflow:</p>
+
+<ol>
+<li>When the client accesses the main route, an index page is served, at which point Angular takes over.</li>
+<li>The Angular app immediately “asks” the server if a user is logged in.</li>
+<li>Assuming the server indicates that a user is not logged in, the client is immediately asked to log in.</li>
+<li>Once logged in, the Angular app then tracks the user’s login status.</li>
+</ol>
+
+
+<blockquote><p>This tutorial uses <a href="https://nodejs.org/">NodeJS</a> v4.3.1, <a href="http://expressjs.com/4x/api.html">ExpressJS</a> v4.13.4, <a href="https://docs.mongodb.org/v3.2/">MongoDB</a> v3.2.3, and <a href="https://code.angularjs.org/1.4.9/docs/guide">AngularJS</a> v1.4.9. For a full list of dependencies, please view the <em><a href="https://github.com/mjhea0/mean-auth/blob/master/package.json">package.json</a></em> file.</p></blockquote>
+
+<h2>Getting Started</h2>
+
+<p>First, grab the boilerplate code from the <a href="https://github.com/mjhea0/mean-auth/releases/tag/v1">project repo</a>, install the requirements, and then test out the app:</p>
+
+<pre><code class="sh">$ npm start
+</code></pre>
+
+<p>Navigate to <a href="http://localhost:3000/">http://localhost:3000/</a> and you should see a simple welcome message - “Yo!”. Once you’re finished admiring the page, kill the server, and glance over the code within the project folder:</p>
+
+<pre><code class="sh">├── client
+│ ├── index.html
+│ ├── main.js
+│ └── partials
+│ └── home.html
+├── package.json
+└── server
+ ├── app.js
+ ├── models
+ │ └── user.js
+ ├── routes
+ │ └── api.js
+ └── server.js
+</code></pre>
+
+<p>Nothing too spectacular. You can see that the back-end code resides in the “server” folder, while the front-end code lives in the “client” folder. Explore the files and folders within each.</p>
+
+<h2>Login API</h2>
+
+<p>Let’s start with the back-end API. This is already built out, for your convenience. Why? The focus of this tutorial is mainly on the client-side. If you’re looking for a back-end tutorial for setting up Passport with NodeJS, ExpressJS, and MongoDB take a look at this <a href="http://mherman.org/blog/2015/01/31/local-authentication-with-passport-and-express-4/#.VZCK9xNViko">tutorial</a>.</p>
+
+<h3>User Registration</h3>
+
+<p>Open the “routes” folder and locate the following code:</p>
+
+<pre><code class="javascript">router.post('/register', function(req, res) {
+ User.register(new User({ username: req.body.username }),
+ req.body.password, function(err, account) {
+ if (err) {
+ return res.status(500).json({
+ err: err
+ });
+ }
+ passport.authenticate('local')(req, res, function () {
+ return res.status(200).json({
+ status: 'Registration successful!'
+ });
+ });
+ });
+});
+</code></pre>
+
+<p>Here, we grab the values from the payload sent with the POST request (from the client-side), create a new <code>User</code> instance, and then attempt to add the instance to the database. If this succeeds a user is added, of course, and then we return a JSON response with a <code>status</code> of “success”. If it fails, an “error” response is sent.</p>
+
+<p>Let’s test this via curl. Fire up the server, and then run the following command:</p>
+
+<pre><code class="sh">$ curl -H "Accept: application/json" -H \
+"Content-type: application/json" -X POST \
+-d '{"username": "test@test.com", "password": "test"}' \
+http://localhost:3000/user/register
+</code></pre>
+
+<p>You should see a success message:</p>
+
+<pre><code class="sh">{
+ "status": "Registration successful!"
+}
+</code></pre>
+
+<p>Try it again, with the exact same username and password, and you should see an error:</p>
+
+<pre><code class="sh">{
+ "err": {
+ "name": "UserExistsError",
+ "message": "A user with the given username is already registered"
+ }
+}
+</code></pre>
+
+<p>On to the login…</p>
+
+<h3>User Login</h3>
+
+<pre><code class="javascript">router.post('/login', function(req, res, next) {
+ passport.authenticate('local', function(err, user, info) {
+ if (err) {
+ return next(err);
+ }
+ if (!user) {
+ return res.status(401).json({
+ err: info
+ });
+ }
+ req.logIn(user, function(err) {
+ if (err) {
+ return res.status(500).json({
+ err: 'Could not log in user'
+ });
+ }
+ res.status(200).json({
+ status: 'Login successful!'
+ });
+ });
+ })(req, res, next);
+});
+</code></pre>
+
+<p>This utilizes Passport’s <a href="https://github.com/jaredhanson/passport-local">local strategy</a> to verify the username/email as well as the password. The appropriate response is then returned.</p>
+
+<p>With the server running, test again with curl-</p>
+
+<pre><code class="sh">curl -H "Accept: application/json" -H \
+"Content-type: application/json" -X POST \
+-d '{"username": "test@test.com", "password": "test"}' \
+http://localhost:3000/user/login
+</code></pre>
+
+<p>-and you should see:</p>
+
+<pre><code class="sh">{
+ "message": "Login successful!"
+}
+</code></pre>
+
+<p>Test again with curl, sending the wrong password, and you should see:</p>
+
+<pre><code class="sh">{
+ "err": {
+ "name": "IncorrectPasswordError",
+ "message": "Password or username are incorrect"
+ }
+}
+</code></pre>
+
+<p>Perfect!</p>
+
+<h3>User Logout</h3>
+
+<p>Finally, take a look at the logout:</p>
+
+<pre><code class="javascript">router.get('/logout', function(req, res) {
+ req.logout();
+ res.status(200).json({
+ status: 'Bye!'
+ });
+});
+</code></pre>
+
+<p>This should be straightforward, and you can probably guess what the response will look like - but let’s test it again to be sure:</p>
+
+<pre><code class="sh">$ curl -H "Accept: application/json" -H \
+"Content-type: application/json" -X GET \
+http://localhost:3000/user/logout
+</code></pre>
+
+<p>You should see:</p>
+
+<pre><code class="sh">{
+ "status": "Bye!"
+}
+</code></pre>
+
+<p>On to the client-side!</p>
+
+<h2>Angular App</h2>
+
+<p>Before diving in, remember that since end users have full access to the power of the browser as well as <a href="https://developer.chrome.com/devtools">DevTools</a> and the client-side code, it’s vital that you not only restrict access to sensitive endpoints on the server-side - but that you also do not store sensitive data on the client-side. Keep this in mind as you add auth functionality to your own MEAN application stack.</p>
+
+<h3>Client-side Routing</h3>
+
+<p>Let’s add the remainder of the client-side routes to the <em>main.js</em> file:</p>
+
+<pre><code class="javascript">myApp.config(function ($routeProvider) {
+ $routeProvider
+ .when('/', {
+ templateUrl: 'partials/home.html'
+ })
+ .when('/login', {
+ templateUrl: 'partials/login.html',
+ controller: 'loginController'
+ })
+ .when('/logout', {
+ controller: 'logoutController'
+ })
+ .when('/register', {
+ templateUrl: 'partials/register.html',
+ controller: 'registerController'
+ })
+ .when('/one', {
+ template: '<h1>This is page one!</h1>'
+ })
+ .when('/two', {
+ template: '<h1>This is page two!</h1>'
+ })
+ .otherwise({
+ redirectTo: '/'
+ });
+});
+</code></pre>
+
+<p>Here, we created five new routes. Before we add the subsequent templates and controllers, let’s create a <a href="https://code.angularjs.org/1.4.9/docs/guide/services">service</a> to handle authentication.</p>
+
+<h3>Authentication Service</h3>
+
+<p>Start by adding the basic structure of the service to a new file called <em>services.js</em> in the “client” directory:</p>
+
+<pre><code class="javascript">angular.module('myApp').factory('AuthService',
+ ['$q', '$timeout', '$http',
+ function ($q, $timeout, $http) {
+
+ // create user variable
+ var user = null;
+
+ // return available functions for use in the controllers
+ return ({
+ isLoggedIn: isLoggedIn,
+ getUserStatus: getUserStatus,
+ login: login,
+ logout: logout,
+ register: register
+ });
+
+}]);
+</code></pre>
+
+<p>Here, we simply defined the service name, <code>AuthService</code>, and then injected the dependencies that we will be using - <code>$q</code>, <code>$timeout</code>, <code>$http</code> - and then returned the functions, which we still need to write, for use outside the service.</p>
+
+<p>Make sure to add the script to the <em>index.html</em> file:</p>
+
+<p>
</p>
-<h2>Add values</h2>
+<p>Let’s create each function…</p>
+<p><strong><code>isLoggedIn()</code></strong></p>
-<p><inputtype="number"ng-model="newItem">
-<buttonclass="btn btn-default"ng-click="add()">Add!</button></p>
+<pre><code class="javascript">function isLoggedIn() {
+ if(user) {
+ return true;
+ } else {
+ return false;
+ }
+}
+</code></pre>
-<p>{{ total }}</p>
+<p>This function returns <code>true</code> if <code>user</code> evaluates to <code>true</code> - a user is logged in - otherwise it returns false.</p>
+<p><strong><code>getUserStatus()</code></strong></p>
-<p>
-
-
-
Then test it in the browser.
-
-
Like last time, we simply need to ensure that total is updated appropriately when the end user submits a number in the input box and then clicks the button.
-
-
describe('TestTwoController', function () {
-
- var total = element(by.tagName('p'));
- var numberInputBox = element(by.css('[ng-model="newItem"]'));
- var changeTotalButton = element(by.css('.btn-default'));
-
- beforeEach(function() {
- browser.get('http://localhost:8888/#/two');
- });
-
- it('initially has a total', function () {
- expect(total.getText()).toEqual('6');
- });
-
- it('updates the `total` when a value is added', function () {
- numberInputBox.sendKeys(7);
- changeTotalButton.click();
- numberInputBox.clear();
- expect(total.getText()).toEqual('13');
- numberInputBox.sendKeys(7);
- changeTotalButton.click();
- expect(total.getText()).toEqual('20');
- numberInputBox.clear();
- numberInputBox.sendKeys(-700);
- changeTotalButton.click();
- expect(total.getText()).toEqual('-680');
- });
-
- it('does not update the `total` when an empty value is added', function () {
- numberInputBox.sendKeys('');
- changeTotalButton.click();
- expect(total.getText()).toEqual('6');
- numberInputBox.sendKeys('hi!');
- changeTotalButton.click();
- expect(total.getText()).toEqual('6');
- });
-
-});
-
-
-
Run the tests and you should see:
-
-
6 tests, 9 assertions, 0 failures
-
-
-
Moving along…
-
-
TestThreeController
-
-
You know the drill:
-
-
-
Look at the Angular and HTML code
-
Manually test in the browser
-
Write the e2e test to automate the manual test
-
-
-
-
Try this on your own before looking at the code below.
-
-
describe('TestThreeController', function () {
-
- var modalNumber = element.all(by.tagName('span')).get(1);
- var modalButton = element(by.tagName('button'));
- var iterateButton = element(by.css('[ng-click="changeModalText()"]'));
- var hideButton = element(by.css('[ng-click="$hide()"]'));
- var justSomeText = element(by.tagName('h2'));
-
- beforeEach(function() {
- browser.get('http://localhost:8888/#/three');
- });
-
- it('initially has a modalNumber', function () {
- modalButton.click();
- expect(modalNumber.getText()).toEqual('1');
- });
-
- it('updates the `modalNumber` when a value is added', function () {
- modalButton.click();
- iterateButton.click();
- expect(modalNumber.getText()).toEqual('2');
- iterateButton.click().click().click();
- expect(modalNumber.getText()).toEqual('5');
- hideButton.click();
- expect(justSomeText.getText()).toEqual('Just a modal');
- });
-
-
-
TestFourController
-
-
Since this controller makes an external call to https://api.github.com/repositories you can either mock out (fake) this request using ngMockE2E, like we did for the unit test, or you can actually make the API call. Again, this depends on how expensive the call is and how important the functionality is to your application. In most cases, it’s better to actually make the call since e2e tests should mimic the actual end user experience as much as possible. Plus, unlike unit tests which test implementation, these tests test user behavior, across several independent units - thus, these tests should not be isolated and can rely on making actual API calls either to the back-end or externally.
-
-
describe('TestFourController', function () {
-
- var loadButton = element(by.tagName('button'));
- var ul = element.all(by.tagName('ul'));
- var li = element.all(by.tagName('li'));
-
- beforeEach(function() {
- browser.get('http://localhost:8888/#/four');
- });
-
- it('updates the DOM when the button is clicked', function () {
- expect(ul.count()).toEqual(1);
- expect(li.count()).toEqual(5);
- loadButton.click();
- expect(ul.count()).toEqual(101);
- expect(li.count()).toEqual(105);
- });
-
-});
-
-
-
Here, when the button is clicked, the API call is made and the scope is updated. We then assert that there are 101 UL tags and 105 LI tags, representing a Github username and repo returned from the API call, present on the DOM.
-
-
That’s it!
-
-
Conclusion
-
-
Want more?
-
-
-
Take a look at the Page Objects design pattern and refactor the tests so that they are better organized.
-
Break a test, and then pause the test before the break via browser.pause() and/or browser.debugger() to debug.
-
Test your own Angular app, and then add a link to the comments to get feedback.
-
-
-
-
Be sure to check the Protractor documentation for more. Thanks again for reading, and happy testing!
-
-
-
-
-
Interested in learning how to test an Angular + Django app? Check out Real Python for details.
-]]>
-
-
-
-
-
- 2015-04-09T09:06:00-06:00
- http://mherman.org/blog/2015/04/09/testing-angularjs-with-protractor-and-karma-part-1
- This article details how to test a simple AngularJS application using unit tests and end-to-end (E2E) tests.
-
-
-
-
-
-
-
-
-
-
Part 1 - In the first part we’ll look at unit tests, which ensure that small, isolated pieces of code (e.g., a unit) behave as expected (current).
-
Part 2 - In part two we’ll address E2E tests, which verify that all the pieces of code (units) fit together by simulating the user experience through browser automation.
-
-
-
-
Updates: December 3rd, 2016 - bumped dependencies
-
-
To accomplish this we will be using Karma v0.12.31 (test runner) and Chai v2.2.0 (assertions) for the unit tests (along with Karma-Mocha) and Protractor v2.0.0 for the E2E tests. This article also uses Angular v1.3.15. Be sure to take note of all dependencies and their versions in the package.json and bower.json files in the repo.
Test it out. Once done, kill the server and checkout the second tag:
-
-
$ git checkout tags/v2
-
-
-
There should now be a “tests” folder and a few more tasks in the Gulpfile.
-
-
Run the unit tests:
-
-
$ gulp unit
-
-
-
They should pass:
-
-
[05:28:02] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
-[05:28:02] Starting 'unit'...
-INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
-INFO [launcher]: Starting browser Chrome
-INFO [Chrome 41.0.2272 (Mac OS X 10.10.2)]: Connected on socket JBQp0aEyu8KSqUfGoxsd with id 94772581
-Chrome 41.0.2272 (Mac OS X 10.10.2): Executed 2 of 2 SUCCESS (0.061 secs / 0.002 secs)
-[05:28:05] Finished 'unit' after 3.23 s
-
-
-
Now for the e2e tests:
-
-
-
1st terminal window: webdriver-manager start
-
2nd terminal window (within the project directory): gulp
-
3rd terminal window (within the project directory): gulp e2e
-
-
-
-
They should pass as well:
-
-
[05:29:45] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
-[05:29:45] Starting 'e2e'...
-Using the selenium server at http://localhost:4444/wd/hub
-[launcher] Running 1 instances of WebDriver
-.
-
-Finished in 0.921 seconds
-1 test, 1 assertion, 0 failures
-
-[launcher] 0 instance(s) of WebDriver still running
-[launcher] chrome #1 passed
-
-
-
So, what’s happening here…
-
-
Configuration Files
-
-
There are two configuration files in the “tests” folder - one for Karma and the other for Protractor.
-
-
Karma
-
-
Karma is a test runner built by the AngularJS team that executes the unit tests and reports the results.
-
-
Let’s look the config file, karma.conf.js:
-
-
module.exports = function(config) {
- config.set({
-
- // base path that will be used to resolve all patterns
- basePath: '.',
-
- // frameworks to use
- frameworks: ['mocha', 'chai'],
-
- // list of files / patterns to load in the browser
- files: [
- '../app/bower_components/angular/angular.js',
- '../app/bower_components/jquery/dist/jquery.js',
- '../app/bower_components/angular-strap/dist/angular-strap.js',
- '../app/bower_components/angular-strap/dist/angular-strap.tpl.js',
- '../app/bower_components/angular-mocks/angular-mocks.js',
- '../app/bower_components/angular-route/angular-route.js',
- './unit/*.js',
- '../app/app.js'
- ],
-
- // test result reporter
- reporters: ['progress'],
-
- // web server port
- port: 9876,
-
- // enable / disable colors in the output (reporters and logs)
- colors: true,
-
- // level of logging
- logLevel: config.LOG_INFO,
-
- // enable / disable watching file and executing tests whenever any file changes
- autoWatch: true,
-
- // start these browsers
- browsers: ['Chrome'],
-
- // Continuous Integration mode
- singleRun: false
- });
-};
-
-
-
You can also run karma init to be guided through the creation of a config file.
-
-
Be sure to read over the comments for an overview of each config option. For more information, review the official documentation.
-
-
Protractor
-
-
Protractor provides a nice wrapper around WebDriverJS, the JavaScript bindings for Selenium Webdriver, to run tests against an AngularJS application running live in a browser.
-
-
Turn your attention to the Protractor config file, protractor.conf.js:
This tells protractor where to find the test files (called specs) and specifies the address that the Selenium server is running on. Simple.
-
-
Ready to start testing?
-
-
Unit Tests
-
-
We’ll start with unit tests since they are much easier to write, debug, and maintain.
-
-
Keep in mind that unit tests, by definition, only test isolated units of code so they rely heavily on mocking fake data. This can add much complexity to your tests and can decrease the effectiveness of the actual tests. For example, if you’re mocking out an HTTP request to a back-end API, then you’re not really testing your application. Instead you’re simulating the request and then using fake JSON data to simulate the response back. The tests may run faster, but they are much less effective.
-
-
When starting out, mock out only the most expensive requests and make the actual API call in other situations. Over time you will develop a better sense of which requests should be mocked and which should not.
-
-
Finally, if you decide not to mock a request in a specific test, then the test is no longer a unit test since it’s not testing an isolated unit of code. Instead you are testing multiple units, which is an integration test. For simplicity, we will continue to refer to such tests as unit tests.
-
-
With that, let’s create some tests, broken up by controller!
The describe block is used to group similar tests.
-
The module, myApp, is loaded, into each test, in the first beforeEach block, which instantiates a clean testing environment.
-
The dependencies are injected, a new scope is created, and the controller is instantiated in the second beforeEach.
-
Each it function is a separate test, which includes a title, in human readable form, and a function with the actual test code.
-
The first test asserts that the initial state of greeting is "Hello, World!".
-
Meanwhile, the second test assets that the changeGreeting() function actually changes the value of greeting.
-
-
-
-
Make sense?
-
-
In most cases, unit tests simply change the scope and assert that the results are what we expected.
-
-
In general, when testing controllers, you inject then register the controller with a beforeEach block, along with the $rootScope and then test that the functions within the controller act as expected.
-
-
Run the tests again to ensure they still pass - gulp unit.
-
-
What else could we test? How about if newText doesn’t change - e.g., if the user submits the button without entering any text in the input box - then the value of greeting should stay the same. Try writing this on your own, before you look at my answer:
-
-
it('clicking the button does not change the greeting if text is not inputed', function () {
- $scope.changeGreeting();
- assert.equal($scope.greeting, "Hello, World!");
-});
-
-
-
Try running this. It should fail.
-
-
Chrome 41.0.2272 (Mac OS X 10.10.2) TestOneController clicking the button does not change the greeting FAILED
- AssertionError: expected undefined to equal 'Hello, World!'
-
-
-
So, we’ve revealed a bug. We could fix this by adding validation to the input box to ensure the end user enters a value or we could update changeGreeting to only update greeting if newText is not undefined. Let’s go with the latter.
$ gulp unit
-[08:28:18] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
-[08:28:18] Starting 'unit'...
-INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
-INFO [launcher]: Starting browser Chrome
-INFO [Chrome 41.0.2272 (Mac OS X 10.10.2)]: Connected on socket HGnVC5-cAXOZjAsrSCWj with id 83240025
-Chrome 41.0.2272 (Mac OS X 10.10.2): Executed 3 of 3 SUCCESS (0.065 secs / 0.001 secs)
-[08:28:21] Finished 'unit' after 3.13 s
-
-
-
Nice!
-
-
Since controllers are used to bind data to the template (via scope), unit tests are perfect for testing the controller logic - e.g., what happens to the scope as the controller runs - while E2E tests ensure that the template is updated accordingly.
What should we test? Take out a pen and paper and write down everything that should be tested. Once done, write the code. Check your code against mine.
it('initially has a total', function () {
- assert.equal($scope.total, 6);
-});
-
-
-
Test 2: The initial value of items
-
-
it('initially has items', function () {
- assert.isArray($scope.items);
- assert.deepEqual($scope.items, [1, 2, 3]);
-});
-
-
-
Test 3: The add function updates the total and items array when a value is added
-
-
it('the `add` function updates the `total` and `items` array when a value is added', function () {
- $scope.newItem = 7;
- $scope.add();
- assert.equal($scope.total, 13);
- assert.deepEqual($scope.items, [1, 2, 3, 7]);
-});
-
-
-
Test 4: The add function does not update the total and items array when an empty value is added
-
-
it('does not update the `total` and `items` array when an empty value is added', function () {
- $scope.newItem = undefined;
- $scope.add();
- assert.equal($scope.total, 6);
- assert.deepEqual($scope.items, [1, 2, 3]);
- $scope.newItem = 22;
- $scope.add();
- assert.equal($scope.total, 28);
- assert.deepEqual($scope.items, [1, 2, 3, 22]);
-});
-
-
-
Run
-
-
Each test should be straightforward. Run the tests. There should be one failure:
-
-
Chrome 41.0.2272 (Mac OS X 10.10.2) TestTwoController does not update the `total` and `items` array when an empty value is added FAILED
- AssertionError: expected NaN to equal 6
-
$ gulp unit
-[09:56:10] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
-[09:56:10] Starting 'unit'...
-INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
-INFO [launcher]: Starting browser Chrome
-INFO [Chrome 41.0.2272 (Mac OS X 10.10.2)]: Connected on socket Lbv1sROpYrEHgotlmJZf with id 91008249
-Chrome 41.0.2272 (Mac OS X 10.10.2): Executed 7 of 7 SUCCESS (0.082 secs / 0.003 secs)
-[09:56:13] Finished 'unit' after 3.05 s
-
-
-
Success!
-
-
Did I miss anything? Comment below.
-
-
TestThreeController
-
-
Again, check out the code in app.js:
-
-
myApp.controller('TestThreeController', function($scope) {
- $scope.modal = {title: 'Hi!', content: 'This is a message!'};
-});
-
-
-
What can we test here?
-
-
it('initially has a modal', function () {
- assert.isObject($scope.modal);
- assert.deepEqual($scope.modal, {title: 'Hi!', content: 'This is a message!'});
-});
-
-
-
Perhaps a better question is: What should we test here? Is the above test really necessary? Probably not. But we may need to test it out more in the future if we build out the functionality. Let’s go for it!
Here we are defined a custom template, modal.tpl.html, to be used for the modal text and then we assigned $scope.modalNumber to 1 as well as function to iterate the number.
-
-
Add modal.tpl.html:
-
-
1
+<pre><code class="javascript">function getUserStatus() {
+ return user;
+}
+</code></pre>
+
+<p><strong><code>login()</code></strong></p>
+
+<pre><code class="javascript">function login(username, password) {
+
+ // create a new instance of deferred
+ var deferred = $q.defer();
+
+ // send a post request to the server
+ $http.post('/user/login',
+ {username: username, password: password})
+ // handle success
+ .success(function (data, status) {
+ if(status === 200 && data.status){
+ user = true;
+ deferred.resolve();
+ } else {
+ user = false;
+ deferred.reject();
+ }
+ })
+ // handle error
+ .error(function (data) {
+ user = false;
+ deferred.reject();
+ });
+
+ // return promise object
+ return deferred.promise;
+
+}
+</code></pre>
+
+<p>Here, we used the <a href="https://code.angularjs.org/1.4.9/docs/api/ng/service/$q">$q</a> service to set up a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promise</a>, which we’ll access in a future controller. We also utilized the <a href="https://code.angularjs.org/1.4.9/docs/api/ng/service/$http">$http</a> service to send an AJAX request to the <code>/user/login</code> endpoint that we already set up in our back-end Node/Express app.</p>
+
+<p>Based on the returned response, we either <a href="https://code.angularjs.org/1.4.9/docs/api/ng/service/$q#usage">resolve</a> or <a href="https://code.angularjs.org/1.4.9/docs/api/ng/service/$q#usage">reject</a> and set the value of <code>user</code> to <code>true</code> or <code>false</code>, respectively.</p>
+
+<p><strong><code>logout()</code></strong></p>
+
+<pre><code class="javascript">function logout() {
+
+ // create a new instance of deferred
+ var deferred = $q.defer();
+
+ // send a get request to the server
+ $http.get('/user/logout')
+ // handle success
+ .success(function (data) {
+ user = false;
+ deferred.resolve();
+ })
+ // handle error
+ .error(function (data) {
+ user = false;
+ deferred.reject();
+ });
+
+ // return promise object
+ return deferred.promise;
+
+}
+</code></pre>
+
+<p>Here, we followed the same formula as the <code>login()</code> function, except we sent a GET request rather than a POST and to be safe we just went ahead and handled the error the same as the success.</p>
+
+<p><strong><code>register()</code></strong></p>
+
+<pre><code class="javascript">function register(username, password) {
+
+ // create a new instance of deferred
+ var deferred = $q.defer();
+
+ // send a post request to the server
+ $http.post('/user/register',
+ {username: username, password: password})
+ // handle success
+ .success(function (data, status) {
+ if(status === 200 && data.status){
+ deferred.resolve();
+ } else {
+ deferred.reject();
+ }
+ })
+ // handle error
+ .error(function (data) {
+ deferred.reject();
+ });
+
+ // return promise object
+ return deferred.promise;
+
+}
+</code></pre>
+
+<p>Again, we followed a similar formula to the <code>logout()</code> function. Can you tell what’s happening?</p>
+
+<p>That’s it for the service. Keep in mind that we still have not “used” this service. In order to do that we just need to inject it into the necessary components in the Angular app. In our case, that will be the controllers, which we’ll build next.</p>
+
+<h3>Templates and Controllers</h3>
+
+<p>Looking back at our routes, we need to setup two partials/templates and three controllers:</p>
+
+<pre><code class="javascript">.when('/login', {
+ templateUrl: 'partials/login.html',
+ controller: 'loginController'
+})
+.when('/logout', {
+ controller: 'logoutController'
+})
+.when('/register', {
+ templateUrl: 'partials/register.html',
+ controller: 'registerController'
+})
+</code></pre>
+
+<p><strong>Login</strong></p>
+
+<p>First, add the following HTML to a new file called <em>login.html</em>:</p>
+
+<p>
</p>
+
+<p>Add this file to the “partials” directory.</p>
+
+<p>Take note of the form. We used the <a href="https://code.angularjs.org/1.4.9/docs/api/ng/directive/ngModel">ng-model</a> directive on each of the inputs so that we can capture those values in the controller. Also, when the form is submitted, the <a href="https://code.angularjs.org/1.4.9/docs/api/ng/directive/ngSubmit">ng-submit</a> directive handles the event by firing the <code>login()</code> function.</p>
+
+<p>Next, within the “client” folder, add a new file called <em>controllers.js</em>. Yes, this will hold all of our Angular app’s controllers. Don’t forget to add the script to the <em>index.html</em> file:</p>
+
+<p>
</p>
+
+<p>Now, let’s add the first controller:</p>
+
+<pre><code class="javascript">angular.module('myApp').controller('loginController',
+ ['$scope', '$location', 'AuthService',
+ function ($scope, $location, AuthService) {
+
+ $scope.login = function () {
+
+ // initial values
+ $scope.error = false;
+ $scope.disabled = true;
+
+ // call login from service
+ AuthService.login($scope.loginForm.username, $scope.loginForm.password)
+ // handle success
+ .then(function () {
+ $location.path('/');
+ $scope.disabled = false;
+ $scope.loginForm = {};
+ })
+ // handle error
+ .catch(function () {
+ $scope.error = true;
+ $scope.errorMessage = "Invalid username and/or password";
+ $scope.disabled = false;
+ $scope.loginForm = {};
+ });
+
+ };
+
+}]);
+</code></pre>
+
+<p>So, when the <code>login()</code> function is fired, we set some initial values and then call <code>login()</code> from the <code>AuthService</code>, passing the user inputed email and password as arguments. The subsequent success or error is then handled and the DOM/view/template is updated appropriately.</p>
+
+<p>Ready to test the first round-trip - <strong>client => server => client</strong>?</p>
+
+<p>Fire up the server and navigate to <a href="http://localhost:3000/#/login">http://localhost:3000/#/login</a> in your browser. First, try logging in with the user credentials used to register earlier - e.g, <code>test@test.com</code> and <code>test</code>, respectively. If all went well, you should be redirected to the main URL. Next, try to log in using invalid credentials. You should see the error message flash, “Invalid username and/or password”.</p>
+
+<p><strong>Logout</strong></p>
+
+<p>Add the controller:</p>
+
+<pre><code class="javascript">angular.module('myApp').controller('logoutController',
+ ['$scope', '$location', 'AuthService',
+ function ($scope, $location, AuthService) {
+
+ $scope.logout = function () {
+
+ // call logout from service
+ AuthService.logout()
+ .then(function () {
+ $location.path('/login');
+ });
+
+ };
+
+}]);
+</code></pre>
+
+<p>Here, we called <code>AuthService.logout()</code> and then redirected the user to the <code>/login</code> route after the promise is resolved.</p>
+
+<p>Add a button to <em>home.html</em>:</p>
+
+<p>
</p>
+
+<p>And then test it out again.</p>
+
+<p><strong>Register</strong></p>
+
+<p>Add a new new file called <em>register.html</em> to the “partials” folder and add the following HTML:</p>
+
+<p>
</p>
+
+<p>Next, add the controller:</p>
+
+<pre><code class="javascript">angular.module('myApp').controller('registerController',
+ ['$scope', '$location', 'AuthService',
+ function ($scope, $location, AuthService) {
+
+ $scope.register = function () {
+
+ // initial values
+ $scope.error = false;
+ $scope.disabled = true;
+
+ // call register from service
+ AuthService.register($scope.registerForm.username, $scope.registerForm.password)
+ // handle success
+ .then(function () {
+ $location.path('/login');
+ $scope.disabled = false;
+ $scope.registerForm = {};
+ })
+ // handle error
+ .catch(function () {
+ $scope.error = true;
+ $scope.errorMessage = "Something went wrong!";
+ $scope.disabled = false;
+ $scope.registerForm = {};
+ });
+
+ };
+
+}]);
+</code></pre>
+
+<p>You’ve seen this before, so let’s move right on to testing.</p>
+
+<p>Fire up the server and register a new user at <a href="http://localhost:3000/#/register">http://localhost:3000/#/register</a>. Make sure to test logging in with that new user as well.</p>
+
+<p>Well, that’s it for the templates and controllers. We now need to add in functionality to check if a user is logged in on each and every change of route.</p>
+
+<h3>Route Changes</h3>
+
+<p>Start by adding the following code to <em>main.js</em>:</p>
+
+<pre><code class="javascript">myApp.run(function ($rootScope, $location, $route, AuthService) {
+ $rootScope.$on('$routeChangeStart',
+ function (event, next, current) {
+ if (AuthService.isLoggedIn() === false) {
+ $location.path('/login');
+ }
+ });
+});
+</code></pre>
+
+<p>The <a href="https://code.angularjs.org/1.4.9/docs/api/ngRoute/service/$route">$routeChangeStart</a> event fires before the actual route change occurs. So, whenever a route is accessed, before the view is served, we ensure that the user is logged in. Test this out!</p>
+
+<h2>Route Restriction</h2>
+
+<p>Right now all client-side routes require a user to be logged in. What if you want certain routes restricted and other routes open?</p>
+
+<p>You can add the following code to each route handler, replacing <code>true</code> with <code>false</code> for routes that you do not want to restrict:</p>
+
+<pre><code class="javascript">access: {restricted: true}
+</code></pre>
+
+<p>For example:</p>
+
+<pre><code class="javascript">myApp.config(function ($routeProvider) {
+ $routeProvider
+ .when('/', {
+ templateUrl: 'partials/home.html',
+ access: {restricted: true}
+ })
+ .when('/login', {
+ templateUrl: 'partials/login.html',
+ controller: 'loginController',
+ access: {restricted: false}
+ })
+ .when('/logout', {
+ controller: 'logoutController',
+ access: {restricted: true}
+ })
+ .when('/register', {
+ templateUrl: 'partials/register.html',
+ controller: 'registerController',
+ access: {restricted: false}
+ })
+ .when('/one', {
+ template: '<h1>This is page one!</h1>',
+ access: {restricted: true}
+ })
+ .when('/two', {
+ template: '<h1>This is page two!</h1>',
+ access: {restricted: false}
+ })
+ .otherwise({
+ redirectTo: '/'
+ });
+});
+</code></pre>
+
+<p>Now just update the <code>$routeChangeStart</code> code in <em>main.js</em>:</p>
+
+<pre><code class="javascript">myApp.run(function ($rootScope, $location, $route, AuthService) {
+ $rootScope.$on('$routeChangeStart',
+ function (event, next, current) {
+ if (next.access.restricted && AuthService.isLoggedIn() === false) {
+ $location.path('/login');
+ $route.reload();
+ }
+ });
+});
+</code></pre>
+
+<p>Test it out!</p>
+
+<h2>Persistant Login</h2>
+
+<p>Finally, what happens on a page refresh? Try it.</p>
+
+<p>The user is logged out, right? Why? Because the controller and services are called again, setting the <code>user</code> variable to <code>null</code>. This is a problem since the user is still logged in on the server side.</p>
+
+<p>Fortunately, the fix is simple: Within the <code>$routeChangeStart</code> we need to ALWAYS check if a user is logged in. Right now, it’s checking whether <code>isLoggedIn()</code> is <code>false</code>. Let’s update <code>getUserStatus()</code> so that it checks the user status on the back-end:</p>
+
+<pre><code class="javascript">function getUserStatus() {
+ return $http.get('/user/status')
+ // handle success
+ .success(function (data) {
+ if(data.status){
+ user = true;
+ } else {
+ user = false;
+ }
+ })
+ // handle error
+ .error(function (data) {
+ user = false;
+ });
+}
+</code></pre>
+
+<p>Then add the route handler:</p>
+
+<pre><code class="javascript">router.get('/status', function(req, res) {
+ if (!req.isAuthenticated()) {
+ return res.status(200).json({
+ status: false
+ });
+ }
+ res.status(200).json({
+ status: true
+ });
+});
+</code></pre>
+
+<p>Finally, update the <code>$routeChangeStart</code>:</p>
+
+<pre><code class="javascript">myApp.run(function ($rootScope, $location, $route, AuthService) {
+ $rootScope.$on('$routeChangeStart',
+ function (event, next, current) {
+ AuthService.getUserStatus()
+ .then(function(){
+ if (next.access.restricted && !AuthService.isLoggedIn()){
+ $location.path('/login');
+ $route.reload();
+ }
+ });
+ });
+});
+</code></pre>
+
+<p>Try it out!</p>
+
+<h2>Conclusion</h2>
+
+<p>That’s it. One thing you should note is that the Angular app can be used with various frameworks as long as the endpoints are set up correctly in the AJAX requests. So, you can easily take the Angular portion and add it to your Django or Pyramid or NodeJS app. Try it!</p>
+
+<blockquote><p>Check out a Python/Flask app with Angular Auth <a href="https://realpython.com/blog/python/handling-user-authentication-with-angular-and-flask/">here</a></p></blockquote>
+
+<p>Grab the final code from the <a href="https://github.com/mjhea0/mean-auth">repo</a>. Cheers!</p>
+]]></content>
+ </entry>
+
+ <entry>
+ <title type="html"><![CDATA[Testing AngularJS With Protractor and Karma - Part 2]]></title>
+ <link href="http://mherman.org/blog/2015/04/26/testing-angularjs-with-protractor-and-karma-part-2/"/>
+ <updated>2015-04-26T08:06:00-06:00</updated>
+ <id>http://mherman.org/blog/2015/04/26/testing-angularjs-with-protractor-and-karma-part-2</id>
+ <content type="html"><![CDATA[<p><strong>This article details how to test a simple AngularJS application using unit tests and end-to-end (E2E) tests.</strong></p>
+
+<div style="text-align:center;">
+ <img src="https://raw.githubusercontent.com/mjhea0/angular-testing-tutorial/master/img/angular-protractor.png" style="max-width: 100%; border:0;" alt="angular + protractor">
+</div>
+
+
+<p><br></p>
+
+<ul>
+<li><a href="http://mherman.org/blog/2015/04/09/testing-angularjs-with-protractor-and-karma-part-1">Part 1</a> - In the first part we’ll look at unit tests, which ensure that small, isolated pieces of code (e.g., a unit) behave as expected.</li>
+<li>Part 2 - In part two we’ll address E2E tests, which verify that all the pieces of code (units) fit together by simulating the user experience through browser automation. <strong>(current)</strong></li>
+</ul>
+
+
+<blockquote><p><em>Updates</em>: December 3rd, 2016 - bumped dependencies</p></blockquote>
+
+<p>Having finished up unit testing, let’s now turn our attention to e2e testing using <a href="http://angular.github.io/protractor/#/">Protractor</a>, which is a testing framework built specifically for AngularJS apps. Essentially, it runs tests against an app in the browser via <a href="http://seleniumhq.github.io/selenium/docs/api/javascript/">Selenium Webdriver</a>, interacting with the app from an end user’s perspective.</p>
+
+<div style="text-align:center;">
+ <img src="https://raw.githubusercontent.com/mjhea0/angular-testing-tutorial/master/img/components.png" style="max-width: 100%; border:0;" alt="protractor components">
+</div>
+
+
+<p><br></p>
+
+<p>Since e2e tests are much more expensive than unit tests - e.g., they generally take more time to run and are harder to write and maintain - you should almost always focus the majority of your testing efforts on unit tests. It’s good to follow the 80/20 rule - 80% of your tests are unit tests, while 20% are e2e tests. That said, this tutorial series breaks this rule since the goal is to educate. Keep this in mind as you write your own tests against your own application.</p>
+
+<p>Also, make sure you test the most important aspects/functions of your application with your e2e tests. Don’t waste time on the trivial. Again, they are expensive, so make each one count.</p>
+
+<p>The <a href="https://github.com/mjhea0/angular-testing-tutorial">repo</a> includes the following tags:</p>
+
+<ol>
+<li><em>v1</em> - <a href="https://github.com/mjhea0/angular-testing-tutorial/releases/tag/v1">project boilerplate</a></li>
+<li><em>v2</em> - <a href="https://github.com/mjhea0/angular-testing-tutorial/releases/tag/v2">adds testing boilerplate/configuration</a></li>
+<li><em>v3</em> - <a href="https://github.com/mjhea0/angular-testing-tutorial/releases/tag/v3">adds unit tests</a></li>
+<li><em>v4</em> - <a href="https://github.com/mjhea0/angular-testing-tutorial/releases/tag/v4">adds E2E tests</a></li>
+</ol>
+
+
+<h2>Project Setup</h2>
+
+<p>Assuming you followed the <a href="http://mherman.org/blog/2015/04/09/testing-angularjs-with-protractor-and-karma-part-1">first part</a> of this tutorial, checkout the third tag, <code>v3</code>, and then run the current test suite starting with the unit tests:</p>
+
+<pre><code class="sh">$ git checkout tags/v3
+$ gulp unit
+
+[23:30:01] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
+[23:30:01] Starting 'unit'...
+INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
+INFO [launcher]: Starting browser Chrome
+INFO [Chrome 42.0.2311 (Mac OS X 10.10.2)]: Connected on socket i04LmGbgt7P1lNIUTgIJ with id 48442826
+Chrome 42.0.2311 (Mac OS X 10.10.2): Executed 12 of 12 SUCCESS (0.236 secs / 0.051 secs)
+[23:30:06] Finished 'unit' after 4.43 s
+</code></pre>
+
+<p>For the e2e tests, you’ll need to open two new terminal windows. In the first new window, run <code>webdriver-manager start</code>. In the second, navigate to your project directory and then run the app - <code>gulp</code>.</p>
+
+<p>Finally, back in the original window, run the tests:</p>
+
+<pre><code class="sh">$ gulp e2e
+
+[23:31:11] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
+[23:31:11] Starting 'e2e'...
+Using the selenium server at http://localhost:4444/wd/hub
+[launcher] Running 1 instances of WebDriver
+.
+
+Finished in 1.174 seconds
+1 test, 1 assertion, 0 failures
+
+[launcher] 0 instance(s) of WebDriver still running
+[launcher] chrome #1 passed
+</code></pre>
+
+<p>Everything look good?</p>
+
+<h2>The Tests</h2>
+
+<p>Open the test spec, <em>spec.js</em>, within the “tests/e2e” directory. Let’s look at the first test:</p>
+
+<pre><code class="javascript">describe('myController', function () {
+
+ it('the dom initially has a greeting', function () {
+ browser.get('http://localhost:8888/#/one');
+ expect(element(by.id('greeting')).getText()).toEqual('Hello, World!');
+ });
+
+});
+</code></pre>
+
+<p>Notice how we’re still using <a href="http://mochajs.org/">Mocha</a> and <a href="http://chaijs.com/">Chai</a> to <a href="http://angular.github.io/protractor/#/frameworks">manage/structure</a> the test so that it simply opens <code>http://localhost:8888/#/one</code> and then asserts that the text within the HTML element with an ID of <code>greeting</code> is <code>Hello, World!</code>. Simple, right?</p>
+
+<p>Let’s take a quick look at the Angular services that we’re using:</p>
+
+<ol>
+<li><a href="http://angular.github.io/protractor/#/api?view=Protractor">browser</a> - loads the page in the browser</li>
+<li><a href="http://angular.github.io/protractor/#/api?view=ElementFinder">element</a> - interacts with the page</li>
+<li><a href="http://angular.github.io/protractor/#/api?view=ProtractorBy">by</a> - finds elements within the page</li>
+</ol>
+
+
+<p>Finally, one important thing to note is how these tests run. Notice that there’s no callbacks and/or promises in the test. How does that work with asynchronous code? Simple: Protractor continues to check each assertion until it passes or a certain amount of <a href="https://github.com/angular/protractor/blob/master/docs/timeouts.md">time</a> passes. There also is a <a href="http://angular.github.io/protractor/#/api?view=then">promise</a> attached to most methods that can be access using <code>then</code>.</p>
+
+<p>With that, let’s write some tests on our own.</p>
+
+<h3>TestOneController</h3>
+
+<p>Just like in the first part, open the controller code:</p>
+
+<pre><code class="javascript">myApp.controller('TestOneController', function($scope) {
+ $scope.greeting = "Hello, World!";
+ $scope.newText = undefined;
+ $scope.changeGreeting = function() {
+ if ($scope.newText !== undefined) {
+ $scope.greeting = $scope.newText;
+ }
+ };
+});
+</code></pre>
+
+<p>How about the HTML?</p>
+
+<p>
</p>
+
+<p>Looking at the Angular code along with the HTML, we know that on the button click, <code>greeting</code> is updated with the user supplied text from the input box. Sound right? Test this out: With the app running via Gulp, navigate to <a href="http://localhost:8888/#/one">http://localhost:8888/#/one</a> and manually test the app to ensure that the controller is working as it should.</p>
+
+<p>Now since we already tested the initial state of <code>greeting</code>, let’s write the test to ensure that the state updates on the button click:</p>
+
+<pre><code class="javascript">describe('TestOneController', function () {
+
+ beforeEach(function() {
+ browser.get('http://localhost:8888/#/one');
+ });
+
+ it('initially has a greeting', function () {
+ expect(element(by.id('greeting')).getText()).toEqual('Hello, World!');
+ });
+
+ it('clicking the button changes the greeting if text is inputed', function () {
+ element(by.css('[ng-model="newText"]')).sendKeys('Hi!');
+ element(by.css('.btn-default')).click();
+ expect(element(by.id('greeting')).getText()).toEqual('Hi!');
+ });
+
+ it('clicking the button does not change the greeting if text is not inputed', function () {
+ element(by.css('.btn-default')).click();
+ expect(element(by.id('greeting')).getText()).toEqual('Hello, World!');
+ });
+
+});
+</code></pre>
+
+<p>So, in both new test cases we’re targeting the input form - via the global <a href="http://angular.github.io/protractor/#/locators">element</a> function - and adding text to it with the <code>sendKeys()</code> method - <code>Hi!</code> in the first test and no text in the second. Then after clicking the button, we’re asserting that the text contained within the HTML element with an id of “greeting” is as expected.</p>
+
+<p>Run the tests. If all went well, you should see:</p>
+
+<pre><code class="sh">[06:15:45] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
+[06:15:45] Starting 'e2e'...
+Using the selenium server at http://localhost:4444/wd/hub
+[launcher] Running 1 instances of WebDriver
+...
+
+Finished in 3.606 seconds
+3 tests, 3 assertions, 0 failures
+
+[launcher] 0 instance(s) of WebDriver still running
+[launcher] chrome #1 passed
+Michaels-MacBook-Pro-3:angular-testing-tutorial michael$
+</code></pre>
+
+<p>Did you see Chrome open in a new window and run the tests, then close itself? It’s super fast!! Want to run the tests in Firefox (or a different <a href="http://angular.github.io/protractor/#/browser-support">browser</a>) as well? Simply update the Protractor config file, <em>protractor.conf.js</em>, like so:</p>
+
+<pre><code class="javascript">exports.config = {
+ seleniumAddress: 'http://localhost:4444/wd/hub',
+ specs: ['tests/e2e/*.js'],
+ multiCapabilities: [{
+ browserName: 'firefox'
+ }, {
+ browserName: 'chrome'
+ }]
+};
+</code></pre>
+
+<p>Test it again. You should now see the tests run in both Chrome and Firefox simultaneously. Nice.</p>
+
+<p>Finally, to simplify the code and speed up the tests (so we only search the DOM once per element), we can assign each element to a variable:</p>
+
+<pre><code class="javascript">describe('TestOneController', function () {
+
+ var greeting = element(by.id('greeting'));
+ var textInputBox = element(by.css('[ng-model="newText"]'));
+ var changeGreetingButton = element(by.css('.btn-default'));
+
+ beforeEach(function() {
+ browser.get('http://localhost:8888/#/one');
+ });
+
+ it('initially has a greeting', function () {
+ expect(greeting.getText()).toEqual('Hello, World!');
+ });
+
+ it('clicking the button changes the greeting if text is inputed', function () {
+ textInputBox.sendKeys('Hi!');
+ changeGreetingButton.click();
+ expect(greeting.getText()).toEqual('Hi!');
+ });
+
+ it('clicking the button does not change the greeting if text is not inputed', function () {
+ textInputBox.sendKeys('');
+ changeGreetingButton.click();
+ expect(greeting.getText()).toEqual('Hello, World!');
+ });
+
+});
+</code></pre>
+
+<p>Test one last time to ensure that this refactor didn’t break anything.</p>
+
+<h3>TestTwoController</h3>
+
+<p>Again, start with the code.</p>
+
+<p>Angular:</p>
+
+<pre><code class="sh">myApp.controller('TestTwoController', function($scope) {
+ $scope.total = 6;
+ $scope.newItem = undefined;
+ $scope.items = [1, 2, 3];
+ $scope.add = function () {
+ if(typeof $scope.newItem == 'number') {
+ $scope.items.push($scope.newItem);
+ $scope.total = 0;
+ for(var i = 0; i < $scope.items.length; i++){
+ $scope.total += parseInt($scope.items[i]);
+ }
+ }
+ };
+});
+</code></pre>
+
+<p>HTML:</p>
+
+<p>
</p>
+
+<p>Then test it in the browser.</p>
+
+<p>Like last time, we simply need to ensure that <code>total</code> is updated appropriately when the end user submits a number in the input box and then clicks the button.</p>
+
+<pre><code class="javascript">describe('TestTwoController', function () {
+
+ var total = element(by.tagName('p'));
+ var numberInputBox = element(by.css('[ng-model="newItem"]'));
+ var changeTotalButton = element(by.css('.btn-default'));
+
+ beforeEach(function() {
+ browser.get('http://localhost:8888/#/two');
+ });
+
+ it('initially has a total', function () {
+ expect(total.getText()).toEqual('6');
+ });
+
+ it('updates the `total` when a value is added', function () {
+ numberInputBox.sendKeys(7);
+ changeTotalButton.click();
+ numberInputBox.clear();
+ expect(total.getText()).toEqual('13');
+ numberInputBox.sendKeys(7);
+ changeTotalButton.click();
+ expect(total.getText()).toEqual('20');
+ numberInputBox.clear();
+ numberInputBox.sendKeys(-700);
+ changeTotalButton.click();
+ expect(total.getText()).toEqual('-680');
+ });
+
+ it('does not update the `total` when an empty value is added', function () {
+ numberInputBox.sendKeys('');
+ changeTotalButton.click();
+ expect(total.getText()).toEqual('6');
+ numberInputBox.sendKeys('hi!');
+ changeTotalButton.click();
+ expect(total.getText()).toEqual('6');
+ });
+
+});
+</code></pre>
+
+<p>Run the tests and you should see:</p>
+
+<pre><code class="sh">6 tests, 9 assertions, 0 failures
+</code></pre>
+
+<p>Moving along…</p>
+
+<h3>TestThreeController</h3>
+
+<p>You know the drill:</p>
+
+<ol>
+<li>Look at the Angular and HTML code</li>
+<li>Manually test in the browser</li>
+<li>Write the e2e test to automate the manual test</li>
+</ol>
+
+
+<p>Try this on your own before looking at the code below.</p>
+
+<pre><code class="javascript">describe('TestThreeController', function () {
+
+ var modalNumber = element.all(by.tagName('span')).get(1);
+ var modalButton = element(by.tagName('button'));
+ var iterateButton = element(by.css('[ng-click="changeModalText()"]'));
+ var hideButton = element(by.css('[ng-click="$hide()"]'));
+ var justSomeText = element(by.tagName('h2'));
+
+ beforeEach(function() {
+ browser.get('http://localhost:8888/#/three');
+ });
+
+ it('initially has a modalNumber', function () {
+ modalButton.click();
+ expect(modalNumber.getText()).toEqual('1');
+ });
+
+ it('updates the `modalNumber` when a value is added', function () {
+ modalButton.click();
+ iterateButton.click();
+ expect(modalNumber.getText()).toEqual('2');
+ iterateButton.click().click().click();
+ expect(modalNumber.getText()).toEqual('5');
+ hideButton.click();
+ expect(justSomeText.getText()).toEqual('Just a modal');
+ });
+</code></pre>
+
+<h3>TestFourController</h3>
+
+<p>Since this controller makes an external call to <a href="https://api.github.com/repositories">https://api.github.com/repositories</a> you can either mock out (fake) this request using <a href="https://docs.angularjs.org/api/ngMockE2E">ngMockE2E</a>, like we did for the unit test, or you can actually make the API call. Again, this depends on how expensive the call is and how important the functionality is to your application. In most cases, it’s better to actually make the call since e2e tests should mimic the actual end user experience as much as possible. Plus, unlike unit tests which test implementation, these tests test user behavior, across several independent units - thus, these tests should not be isolated and can rely on making actual API calls either to the back-end or externally.</p>
+
+<pre><code class="javascript">describe('TestFourController', function () {
+
+ var loadButton = element(by.tagName('button'));
+ var ul = element.all(by.tagName('ul'));
+ var li = element.all(by.tagName('li'));
+
+ beforeEach(function() {
+ browser.get('http://localhost:8888/#/four');
+ });
+
+ it('updates the DOM when the button is clicked', function () {
+ expect(ul.count()).toEqual(1);
+ expect(li.count()).toEqual(5);
+ loadButton.click();
+ expect(ul.count()).toEqual(101);
+ expect(li.count()).toEqual(105);
+ });
+
+});
+</code></pre>
+
+<p>Here, when the button is clicked, the API call is made and the scope is updated. We then assert that there are 101 UL tags and 105 LI tags, representing a Github username and repo returned from the API call, present on the DOM.</p>
+
+<p>That’s it!</p>
+
+<h2>Conclusion</h2>
+
+<p><em>Want more?</em></p>
+
+<ol>
+<li>Take a look at the <a href="http://angular.github.io/protractor/#/page-objects">Page Objects</a> design pattern and refactor the tests so that they are better organized.</li>
+<li>Break a test, and then <a href="http://angular.github.io/protractor/#/debugging#pausing-to-debug">pause</a> the test before the break via <code>browser.pause()</code> and/or <code>browser.debugger()</code> to debug.</li>
+<li>Test your own Angular app, and then add a link to the comments to get feedback.</li>
+</ol>
+
+
+<p>Be sure to check the <a href="http://angular.github.io/protractor/#/">Protractor</a> documentation for more. Thanks again for reading, and happy testing!</p>
+
+<hr><br>
+
+
+<p><em>Interested in learning how to test an Angular + Django app? Check out <a href="http://www.realpython.com/">Real Python</a> for details.</em></p>
+]]></content>
+ </entry>
+
+ <entry>
+ <title type="html"><![CDATA[Testing AngularJS With Protractor and Karma - Part 1]]></title>
+ <link href="http://mherman.org/blog/2015/04/09/testing-angularjs-with-protractor-and-karma-part-1/"/>
+ <updated>2015-04-09T09:06:00-06:00</updated>
+ <id>http://mherman.org/blog/2015/04/09/testing-angularjs-with-protractor-and-karma-part-1</id>
+ <content type="html"><![CDATA[<p><strong>This article details how to test a simple AngularJS application using unit tests and end-to-end (E2E) tests.</strong></p>
+
+<div style="text-align:center;">
+ <img src="https://raw.githubusercontent.com/mjhea0/angular-testing-tutorial/master/img/angular-karma.png" style="max-width: 100%; border:0;" alt="angular + karma">
+</div>
+
+
+<p><br></p>
+
+<ul>
+<li>Part 1 - In the first part we’ll look at unit tests, which ensure that small, isolated pieces of code (e.g., a unit) behave as expected <strong>(current)</strong>.</li>
+<li><a href="http://mherman.org/blog/2015/04/26/testing-angularjs-with-protractor-and-karma-part-2">Part 2</a> - In part two we’ll address E2E tests, which verify that all the pieces of code (units) fit together by simulating the user experience through browser automation.</li>
+</ul>
+
+
+<blockquote><p><em>Updates</em>: December 3rd, 2016 - bumped dependencies</p></blockquote>
+
+<p>To accomplish this we will be using <a href="http://karma-runner.github.io/">Karma</a> v0.12.31 (test runner) and <a href="http://chaijs.com/">Chai</a> v2.2.0 (assertions) for the unit tests (along with <a href="https://github.com/karma-runner/karma-mocha">Karma-Mocha</a>) and <a href="http://angular.github.io/protractor/#/">Protractor</a> v2.0.0 for the E2E tests. This article also uses <a href="https://angularjs.org/">Angular</a> v1.3.15. Be sure to take note of all dependencies and their versions in the <em>package.json</em> and <em>bower.json</em> files in the <a href="https://github.com/mjhea0/angular-testing-tutorial">repo</a>.</p>
+
+<p>The repo includes the following tags:</p>
+
+<ol>
+<li><em>v1</em> - <a href="https://github.com/mjhea0/angular-testing-tutorial/releases/tag/v1">project boilerplate</a></li>
+<li><em>v2</em> - <a href="https://github.com/mjhea0/angular-testing-tutorial/releases/tag/v2">adds testing boilerplate/configuration</a></li>
+<li><em>v3</em> - <a href="https://github.com/mjhea0/angular-testing-tutorial/releases/tag/v3">adds unit tests</a></li>
+<li><em>v4</em> - <a href="https://github.com/mjhea0/angular-testing-tutorial/releases/tag/v4">adds E2E tests</a></li>
+</ol>
+
+
+<h2>Project Setup</h2>
+
+<p>Start by cloning the repo, checkout out the first tag, and then install the dependencies:</p>
+
+<pre><code class="sh">$ git clone https://github.com/mjhea0/angular-testing-tutorial.git
+$ cd angular-testing-tutorial
+$ git checkout tags/v1
+$ npm install && bower install
+</code></pre>
+
+<p>Run the app:</p>
+
+<pre><code class="sh">$ gulp
+</code></pre>
+
+<p>Navigate to <a href="http://localhost:8888">http://localhost:8888</a> to view the live app.</p>
+
+<div style="text-align:center;">
+ <img src="https://raw.githubusercontent.com/mjhea0/angular-testing-tutorial/master/img/live-app.png" style="max-width: 100%; border:0;" alt="angular app">
+</div>
+
+
+<p><br></p>
+
+<p>Test it out. Once done, kill the server and checkout the second tag:</p>
+
+<pre><code class="sh">$ git checkout tags/v2
+</code></pre>
+
+<p>There should now be a “tests” folder and a few more tasks in the Gulpfile.</p>
+
+<p>Run the unit tests:</p>
+
+<pre><code class="sh">$ gulp unit
+</code></pre>
+
+<p>They should pass:</p>
+
+<pre><code class="sh">[05:28:02] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
+[05:28:02] Starting 'unit'...
+INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
+INFO [launcher]: Starting browser Chrome
+INFO [Chrome 41.0.2272 (Mac OS X 10.10.2)]: Connected on socket JBQp0aEyu8KSqUfGoxsd with id 94772581
+Chrome 41.0.2272 (Mac OS X 10.10.2): Executed 2 of 2 SUCCESS (0.061 secs / 0.002 secs)
+[05:28:05] Finished 'unit' after 3.23 s
+</code></pre>
+
+<p>Now for the e2e tests:</p>
+
+<ol>
+<li>1st terminal window: <code>webdriver-manager start</code></li>
+<li>2nd terminal window (within the project directory): <code>gulp</code></li>
+<li>3rd terminal window (within the project directory): <code>gulp e2e</code></li>
+</ol>
+
+
+<p>They should pass as well:</p>
+
+<pre><code class="sh">[05:29:45] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
+[05:29:45] Starting 'e2e'...
+Using the selenium server at http://localhost:4444/wd/hub
+[launcher] Running 1 instances of WebDriver
+.
+
+Finished in 0.921 seconds
+1 test, 1 assertion, 0 failures
+
+[launcher] 0 instance(s) of WebDriver still running
+[launcher] chrome #1 passed
+</code></pre>
+
+<p>So, what’s happening here…</p>
+
+<h2>Configuration Files</h2>
+
+<p>There are two configuration files in the “tests” folder - one for Karma and the other for Protractor.</p>
+
+<h3>Karma</h3>
+
+<p><a href="http://karma-runner.github.io/">Karma</a> is a test runner built by the AngularJS team that executes the unit tests and reports the results.</p>
+
+<p>Let’s look the config file, <em>karma.conf.js</em>:</p>
+
+<pre><code class="javascript">module.exports = function(config) {
+ config.set({
+
+ // base path that will be used to resolve all patterns
+ basePath: '.',
+
+ // frameworks to use
+ frameworks: ['mocha', 'chai'],
+
+ // list of files / patterns to load in the browser
+ files: [
+ '../app/bower_components/angular/angular.js',
+ '../app/bower_components/jquery/dist/jquery.js',
+ '../app/bower_components/angular-strap/dist/angular-strap.js',
+ '../app/bower_components/angular-strap/dist/angular-strap.tpl.js',
+ '../app/bower_components/angular-mocks/angular-mocks.js',
+ '../app/bower_components/angular-route/angular-route.js',
+ './unit/*.js',
+ '../app/app.js'
+ ],
+
+ // test result reporter
+ reporters: ['progress'],
+
+ // web server port
+ port: 9876,
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+ // level of logging
+ logLevel: config.LOG_INFO,
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+ // start these browsers
+ browsers: ['Chrome'],
+
+ // Continuous Integration mode
+ singleRun: false
+ });
+};
+</code></pre>
+
+<blockquote><p>You can also run <code>karma init</code> to be guided through the creation of a config file.</p></blockquote>
+
+<p>Be sure to read over the comments for an overview of each config option. For more information, review the <a href="http://karma-runner.github.io/0.12/config/configuration-file.html">official documentation</a>.</p>
+
+<h3>Protractor</h3>
+
+<p><a href="http://angular.github.io/protractor/#/">Protractor</a> provides a nice wrapper around <a href="https://github.com/SeleniumHQ/selenium/wiki/WebDriverJs">WebDriverJS</a>, the JavaScript bindings for <a href="http://seleniumhq.github.io/selenium/docs/api/javascript/">Selenium Webdriver</a>, to run tests against an AngularJS application running live in a browser.</p>
+
+<p>Turn your attention to the Protractor config file, <em>protractor.conf.js</em>:</p>
+
+<pre><code class="javascript">exports.config = {
+ seleniumAddress: 'http://localhost:4444/wd/hub',
+ specs: ['tests/e2e/*.js']
+};
+</code></pre>
+
+<p>This tells protractor where to find the test files (called specs) and specifies the address that the Selenium server is running on. Simple.</p>
+
+<p>Ready to start testing?</p>
+
+<h2>Unit Tests</h2>
+
+<p>We’ll start with unit tests since they are much easier to write, debug, and maintain.</p>
+
+<p>Keep in mind that unit tests, by definition, only test isolated units of code so they rely heavily on mocking fake data. This can add much complexity to your tests and can decrease the effectiveness of the actual tests. For example, if you’re mocking out an HTTP request to a back-end API, then you’re not really testing your application. Instead you’re simulating the request and then using fake JSON data to simulate the response back. The tests may run faster, but they are much less effective.</p>
+
+<p>When starting out, mock out only the most expensive requests and make the actual API call in other situations. Over time you will develop a better sense of which requests should be mocked and which should not.</p>
+
+<p>Finally, if you decide not to mock a request in a specific test, then the test is no longer a unit test since it’s not testing an isolated unit of code. Instead you are testing multiple units, which is an integration test. For simplicity, we will continue to refer to such tests as unit tests.</p>
+
+<p>With that, let’s create some tests, broken up by controller!</p>
+
+<h3>TestOneController</h3>
+
+<p>Take a look at the code in the first controller:</p>
+
+<pre><code class="javascript">myApp.controller('TestOneController', function($scope) {
+ $scope.greeting = "Hello, World!";
+ $scope.newText = undefined;
+ $scope.changeGreeting = function() {
+ $scope.greeting = $scope.newText;
+ };
+});
+</code></pre>
+
+<p>What’s happening here? Confirm your answer by running your app and watching what happens. Now, what can/should we test?</p>
+
+<ol>
+<li><code>greeting</code> has an initial value of <code>"Hello, World!"</code>, and</li>
+<li>The <code>changeGreeting</code> function updates <code>greeting</code>.</li>
+</ol>
+
+
+<p>You probably noticed that we are already testing this in the spec:</p>
+
+<pre><code class="javascript">describe('TestOneController', function () {
+
+ var controller = null;
+ $scope = null;
+
+ beforeEach(function () {
+ module('myApp');
+ });
+
+ beforeEach(inject(function ($controller, $rootScope) {
+ $scope = $rootScope.$new();
+ controller = $controller('TestOneController', {
+ $scope: $scope
+ });
+ }));
+
+ it('initially has a greeting', function () {
+ assert.equal($scope.greeting, "Hello, World!");
+ });
+
+ it('clicking the button changes the greeting', function () {
+ $scope.newText = "Hi!";
+ $scope.changeGreeting();
+ assert.equal($scope.greeting, "Hi!");
+ });
+
+});
+</code></pre>
+
+<p>What’s happening?</p>
+
+<ol>
+<li>The <code>describe</code> block is used to group similar tests.</li>
+<li>The module, <code>myApp</code>, is loaded, into each test, in the first <code>beforeEach</code> block, which instantiates a clean testing environment.</li>
+<li>The dependencies are injected, a new scope is created, and the controller is instantiated in the second <code>beforeEach</code>.</li>
+<li>Each <code>it</code> function is a separate test, which includes a title, in human readable form, and a function with the actual test code.</li>
+<li>The first test asserts that the initial state of <code>greeting</code> is <code>"Hello, World!"</code>.</li>
+<li>Meanwhile, the second test assets that the <code>changeGreeting()</code> function actually changes the value of <code>greeting</code>.</li>
+</ol>
+
+
+<p>Make sense?</p>
+
+<p><em>In most cases, unit tests simply change the scope and assert that the results are what we expected.</em></p>
+
+<blockquote><p>In general, when testing controllers, you inject then register the controller with a <code>beforeEach</code> block, along with the <code>$rootScope</code> and then test that the functions within the controller act as expected.</p></blockquote>
+
+<p>Run the tests again to ensure they still pass - <code>gulp unit</code>.</p>
+
+<p>What else could we test? How about if <code>newText</code> doesn’t change - e.g., if the user submits the button without entering any text in the input box - then the value of <code>greeting</code> should stay the same. Try writing this on your own, before you look at my answer:</p>
+
+<pre><code class="javascript">it('clicking the button does not change the greeting if text is not inputed', function () {
+ $scope.changeGreeting();
+ assert.equal($scope.greeting, "Hello, World!");
+});
+</code></pre>
+
+<p>Try running this. It should fail.</p>
+
+<pre><code class="sh">Chrome 41.0.2272 (Mac OS X 10.10.2) TestOneController clicking the button does not change the greeting FAILED
+ AssertionError: expected undefined to equal 'Hello, World!'
+</code></pre>
+
+<p>So, we’ve revealed a bug. We could fix this by adding validation to the input box to ensure the end user enters a value or we could update <code>changeGreeting</code> to only update <code>greeting</code> if <code>newText</code> is not <code>undefined</code>. Let’s go with the latter.</p>
+
+<pre><code class="javascript">$scope.changeGreeting = function() {
+ if ($scope.newText !== undefined) {
+ $scope.greeting = $scope.newText;
+ }
+};
+</code></pre>
+
+<p>Save the code, and then run the tests again:</p>
+
+<pre><code class="sh">$ gulp unit
+[08:28:18] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
+[08:28:18] Starting 'unit'...
+INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
+INFO [launcher]: Starting browser Chrome
+INFO [Chrome 41.0.2272 (Mac OS X 10.10.2)]: Connected on socket HGnVC5-cAXOZjAsrSCWj with id 83240025
+Chrome 41.0.2272 (Mac OS X 10.10.2): Executed 3 of 3 SUCCESS (0.065 secs / 0.001 secs)
+[08:28:21] Finished 'unit' after 3.13 s
+</code></pre>
+
+<p>Nice!</p>
+
+<blockquote><p>Since controllers are used to bind data to the template (via scope), unit tests are perfect for testing the controller logic - e.g., what happens to the scope as the controller runs - while E2E tests ensure that the template is updated accordingly.</p></blockquote>
+
+<h3>TestTwoController</h3>
+
+<p>Start by analyzing the code:</p>
+
+<pre><code class="javascript">myApp.controller('TestTwoController', function($scope) {
+ $scope.total = 6;
+ $scope.newItem = undefined;
+ $scope.items = [1, 2, 3];
+ $scope.add = function () {
+ $scope.items.push($scope.newItem);
+ $scope.total = 0;
+ for(var i = 0; i < $scope.items.length; i++){
+ $scope.total += parseInt($scope.items[i]);
+ }
+ };
+});
+</code></pre>
+
+<p>What should we test? Take out a pen and paper and write down everything that should be tested. Once done, write the code. Check your code against mine.</p>
+
+<p>Be sure to start with the following boilerplate:</p>
+
+<pre><code class="javascript">describe('TestTwoController', function () {
+
+ var controller = null;
+ $scope = null;
+
+ beforeEach(function () {
+ module('myApp');
+ });
+
+ beforeEach(inject(function ($controller, $rootScope) {
+ $scope = $rootScope.$new();
+ controller = $controller('TestTwoController', {
+ $scope: $scope
+ });
+ }));
+
+});
+</code></pre>
+
+<h4>Test 1: The initial value of <code>total</code></h4>
+
+<pre><code class="javascript">it('initially has a total', function () {
+ assert.equal($scope.total, 6);
+});
+</code></pre>
+
+<h4>Test 2: The initial value of <code>items</code></h4>
+
+<pre><code class="javascript">it('initially has items', function () {
+ assert.isArray($scope.items);
+ assert.deepEqual($scope.items, [1, 2, 3]);
+});
+</code></pre>
+
+<h4>Test 3: The <code>add</code> function updates the <code>total</code> and <code>items</code> array when a value is added</h4>
+
+<pre><code class="javascript">it('the `add` function updates the `total` and `items` array when a value is added', function () {
+ $scope.newItem = 7;
+ $scope.add();
+ assert.equal($scope.total, 13);
+ assert.deepEqual($scope.items, [1, 2, 3, 7]);
+});
+</code></pre>
+
+<h4>Test 4: The <code>add</code> function does not update the <code>total</code> and <code>items</code> array when an empty value is added</h4>
+
+<pre><code class="javascript">it('does not update the `total` and `items` array when an empty value is added', function () {
+ $scope.newItem = undefined;
+ $scope.add();
+ assert.equal($scope.total, 6);
+ assert.deepEqual($scope.items, [1, 2, 3]);
+ $scope.newItem = 22;
+ $scope.add();
+ assert.equal($scope.total, 28);
+ assert.deepEqual($scope.items, [1, 2, 3, 22]);
+});
+</code></pre>
+
+<h4>Run</h4>
+
+<p>Each test should be straightforward. Run the tests. There should be one failure:</p>
+
+<pre><code class="javascript">Chrome 41.0.2272 (Mac OS X 10.10.2) TestTwoController does not update the `total` and `items` array when an empty value is added FAILED
+ AssertionError: expected NaN to equal 6
+</code></pre>
+
+<p>Update the code, adding a conditional again:</p>
+
+<pre><code class="javascript">$scope.add = function () {
+ if(typeof $scope.newItem == 'number') {
+ $scope.items.push($scope.newItem);
+ $scope.total = 0;
+ for(var i = 0; i < $scope.items.length; i++){
+ $scope.total += parseInt($scope.items[i]);
+ }
+ }
+};
+</code></pre>
+
+<p>Also update the partial, <em>/app/partials/two.html</em>:</p>
+
+<pre><code class="html"><input type="number" ng-model="newItem">
+</code></pre>
+
+<p>Run it again:</p>
+
+<pre><code class="sh">$ gulp unit
+[09:56:10] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
+[09:56:10] Starting 'unit'...
+INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
+INFO [launcher]: Starting browser Chrome
+INFO [Chrome 41.0.2272 (Mac OS X 10.10.2)]: Connected on socket Lbv1sROpYrEHgotlmJZf with id 91008249
+Chrome 41.0.2272 (Mac OS X 10.10.2): Executed 7 of 7 SUCCESS (0.082 secs / 0.003 secs)
+[09:56:13] Finished 'unit' after 3.05 s
+</code></pre>
+
+<p>Success!</p>
+
+<p>Did I miss anything? Comment below.</p>
+
+<h3>TestThreeController</h3>
+
+<p>Again, check out the code in <em>app.js</em>:</p>
+
+<pre><code class="javascript">myApp.controller('TestThreeController', function($scope) {
+ $scope.modal = {title: 'Hi!', content: 'This is a message!'};
+});
+</code></pre>
+
+<p>What can we test here?</p>
+
+<pre><code class="javascript">it('initially has a modal', function () {
+ assert.isObject($scope.modal);
+ assert.deepEqual($scope.modal, {title: 'Hi!', content: 'This is a message!'});
+});
+</code></pre>
+
+<p>Perhaps a better question is: What <em>should</em> we test here? Is the above test really necessary? Probably not. But we may need to test it out more in the future if we build out the functionality. Let’s go for it!</p>
+
+<h4>Update <em>app.js</em>:</h4>
+
+<pre><code class="javascript">myApp.controller('TestThreeController', function($scope, $modal) {
+ $scope.modalNumber = 1;
+ var myModal = $modal({scope: $scope, template: 'modal.tpl.html', show: false});
+ $scope.showModal = function() {
+ myModal.$promise.then(myModal.show);
+ };
+ $scope.changeModalText = function() {
+ $scope.modalNumber++;
+ };
+});
+</code></pre>
+
+<p>Here we are defined a custom template, <code>modal.tpl.html</code>, to be used for the modal text and then we assigned <code>$scope.modalNumber</code> to <code>1</code> as well as function to iterate the number.</p>
+
+<h4>Add <em>modal.tpl.html</em>:</h4>
+
+<p>
1234
@@ -1734,36 +4754,36 @@ Chrome 41.0.2272 (Mac OS X 10.10.2): Executed 7 of 7 SUCCESS (0.082 secs / 0.003
192021
-
</p>
-<h2>Just a modal</h2>
+<p>Run the app to make sure everything works, and then update the test…</p>
+<h4>Test redux</h4>
-<p><buttontype="button"class="btn btn-lg btn-default"data-template="modal.tpl.html"bs-modal="modal">
- Launch modal!
-</button>
-
-
-
Run the app to make sure everything works, and then update the test…
-
-
Test redux
-
-
describe('TestThreeController', function () {
-
- var controller = null;
- $scope = null;
-
- beforeEach(function () {
- module('myApp');
- });
-
- beforeEach(inject(function ($controller, $rootScope) {
- $scope = $rootScope.$new();
- controller = $controller('TestThreeController', {
- $scope: $scope
- });
- }));
-
- it('initially has a modalNumber', function () {
- assert.equal($scope.modalNumber, 1);
- });
-
- it('updates the `modalNumber` when a value is added', function () {
- $scope.changeModalText();
- assert.equal($scope.modalNumber, 2);
- $scope.changeModalText();
- assert.equal($scope.modalNumber, 3);
- });
-
-});
-
-
-
Notice how we’re no longer testing that a modal is present. We’ll test that via the E2E tests.
Remember the discussion earlier on mocking HTTP requests? Well, here’s probably a good place to actually use a mocking library since this request hits an external API. To do this, we can use the $httpBackend directive from the angular-mocks library.
-
-
First, let’s first add the mock.js file found in the repo into a new folder called “mock” within the “tests” folder. This module uses angular.module().value to set a JSON value to use as the fake data.
-
-
Update the list of files in karma.conf.js so that the the mock file is loaded and served by Karma:
Essentially, here we’re injecting defaultJSON so that when the app tries to make the HTTP request, it triggers $httpBackend, which, in turn, uses the defaultJSON value.
-
Did you notice the underscores surrounding the $httpBackend directive? This is a hack that allows us to use the dependency in multiple tests. You can find more information on this from the official documentation.
-
Finally, we’re using an afterEach block to check that we’re not missing any HTTP requests in our tests via the verifyNoOutstandingExpectation() and verifyNoOutstandingRequest() methods. Again, you can read more about these methods from the Angular docs.
When the route is loaded, the current property is updated. We then test to ensure that the current controller and template are TestOneController and partials/one.html, respectively.
-
Did you notice that we wrapped the route change inside the $apply callback? Since unit tests don’t run the full Angular app, we had to simulate it by triggering the digest cycle.
-
Curious about WhenGET? Check out the Angular documentation. Take note of ExpectGET as well. Can you re-write the above test to use ExpectGET?
-
-
-
-
Make sure to run the tests one last time:
-
-
$ gulp unit
-[05:20:07] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
-[05:20:07] Starting 'unit'...
-INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
-INFO [launcher]: Starting browser Chrome
-INFO [Chrome 41.0.2272 (Mac OS X 10.10.2)]: Connected on socket R5qQUcjswAbpcvMK6JKu with id 67365006
-Chrome 41.0.2272 (Mac OS X 10.10.2): Executed 12 of 12 SUCCESS (0.16 secs / 0.027 secs)
-[05:20:10] Finished 'unit' after 3.44 s
-
-
-
Conclusion
-
-
That’s it for unit tests. In the next part, we’ll test the entire application, front to back, using end-to-end (E2E) tests via Protractor.
-
-
Checkout the third tag, v3, to view all the completed unit tests:
-
-
$ git checkout tags/v3
-
-
-
Ready for more?
-
-
Try adding some Factories/Services and Filters to your app to continue practicing. Since the syntax is relatively the same for testing all parts of an Angular app, you should be able to extend your testing knowledge to both factories and filters. Take a look at this example for help getting started. Once you feel comfortable with factories, controllers, and filters, move on to testing more difficult components, like directives, resources, and animations. Good luck!
-
-
Comment below with questions.
-]]>
-
-
-
-
-
- 2014-08-15T09:56:00-06:00
- http://mherman.org/blog/2014/08/15/kickstarting-angular-with-gulp-and-browserify-part-2
- Hello. Welcome to the second half. Last time, we built a nice Angular starter project, utilizing Gulp and Bower. Let’s take this a step further and add the power of Browserify into the mix. Before you read any further, check out the Introduction to the Browserify Handbook to learn about the problems that Browserify solves.
-
-
-### Test
-
-To recap:
-
-1. We added Browserify
-2. Updated the build process so that a single JS file named *bundled.js* is created
-3. Updated *index.html* to include that new JS file
-
-
-
$ gulp
-```
+<pre><code class="javascript">describe('TestThreeController', function () {
+
+ var controller = null;
+ $scope = null;
+
+ beforeEach(function () {
+ module('myApp');
+ });
+
+ beforeEach(inject(function ($controller, $rootScope) {
+ $scope = $rootScope.$new();
+ controller = $controller('TestThreeController', {
+ $scope: $scope
+ });
+ }));
+
+ it('initially has a modalNumber', function () {
+ assert.equal($scope.modalNumber, 1);
+ });
+
+ it('updates the `modalNumber` when a value is added', function () {
+ $scope.changeModalText();
+ assert.equal($scope.modalNumber, 2);
+ $scope.changeModalText();
+ assert.equal($scope.modalNumber, 3);
+ });
+
+});
+</code></pre>
+
+<p>Notice how we’re no longer testing that a modal is present. We’ll test that via the E2E tests.</p>
+
+<h3>TestFourController</h3>
+
+<p>Finally, let’s test the AJAX request:</p>
+
+<pre><code class="sh">myApp.controller('TestFourController', function($scope, $http) {
+ $scope.repos = [];
+ $scope.loadRepos = function () {
+ $http.get('https://api.github.com/repositories').then(function (repos) {
+ $scope.repos = repos.data;
+ });
+ };
+});
+</code></pre>
+
+<p>Remember the discussion earlier on mocking HTTP requests? Well, here’s probably a good place to actually use a mocking library since this request hits an external API. To do this, we can use the <code>$httpBackend</code> directive from the <a href="https://docs.angularjs.org/api/ngMock">angular-mocks</a> library.</p>
+
+<p>First, let’s first add the <em>mock.js</em> file found in the <a href="https://github.com/mjhea0/angular-testing-tutorial/tree/master/tests/mock">repo</a> into a new folder called “mock” within the “tests” folder. This module uses <code>angular.module().value</code> to set a JSON value to use as the fake data.</p>
+
+<p>Update the list of files in <em>karma.conf.js</em> so that the the mock file is loaded and served by Karma:</p>
+
+<pre><code class="javascript">files: [
+ '../app/bower_components/angular/angular.js',
+ '../app/bower_components/jquery/dist/jquery.js',
+ '../app/bower_components/angular-strap/dist/angular-strap.js',
+ '../app/bower_components/angular-strap/dist/angular-strap.tpl.js',
+ '../app/bower_components/angular-mocks/angular-mocks.js',
+ '../app/bower_components/angular-route/angular-route.js',
+ './unit/*.js',
+ './mock/*.js',
+ '../app/app.js'
+],
+</code></pre>
+
+<p>Next, add the test:</p>
+
+<pre><code class="javascript">describe('TestFourController', function () {
+
+ var controller = null;
+ var $scope = null;
+ var $httpBackend = null;
+ var mockedDashboardJSON = null;
+
+ beforeEach(function () {
+ module('myApp', 'mockedDashboardJSON');
+ });
+
+ beforeEach(inject(function ($controller, $rootScope, _$httpBackend_, defaultJSON) {
+ $httpBackend = _$httpBackend_;
+ $scope = $rootScope.$new();
+ $httpBackend.when('GET','https://api.github.com/repositories').respond(defaultJSON.fakeData);
+ controller = $controller('TestFourController', {
+ $scope: $scope
+ });
+ }));
+
+ afterEach(function () {
+ $httpBackend.verifyNoOutstandingExpectation();
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+ it('initially has repos', function () {
+ assert.isArray($scope.repos);
+ assert.deepEqual($scope.repos, []);
+ });
+
+ it('clicking the button updates the repos', function () {
+ $scope.loadRepos();
+ $httpBackend.flush();
+ assert.equal($scope.repos.length, 100);
+ });
+
+});
+</code></pre>
+
+<p>What’s happening?</p>
+
+<ol>
+<li>Essentially, here we’re injecting <code>defaultJSON</code> so that when the app tries to make the HTTP request, it triggers <code>$httpBackend</code>, which, in turn, uses the <code>defaultJSON</code> value.</li>
+<li>Did you notice the underscores surrounding the <code>$httpBackend</code> directive? This is a hack that allows us to use the dependency in multiple tests. You can find more information on this from the <a href="https://docs.angularjs.org/api/ngMock/function/angular.mock.inject">official documentation</a>.</li>
+<li>Finally, we’re using an <code>afterEach</code> block to check that we’re not missing any HTTP requests in our tests via the <code>verifyNoOutstandingExpectation()</code> and <code>verifyNoOutstandingRequest()</code> methods. Again, you can read more about these methods from the <a href="https://docs.angularjs.org/api/ngMock/service/$httpBackend">Angular docs</a>.</li>
+</ol>
+
+
+<p>Test it out!</p>
+
+<h3>Routes</h3>
+
+<p>How about the routes, templates, and partials?</p>
+
+<pre><code class="javascript">describe('routes', function(){
+
+ beforeEach(function () {
+ module('myApp');
+ });
+
+ beforeEach(inject(function (_$httpBackend_, _$route_, _$location_, $rootScope) {
+ $httpBackend = _$httpBackend_;
+ $route = _$route_;
+ $location = _$location_;
+ $scope = $rootScope.$new();
+ }));
+
+ it('should load the one.html template', function(){
+ $httpBackend.whenGET('partials/one.html').respond('...');
+ $scope.$apply(function() {
+ $location.path('/one');
+ });
+ assert.equal($route.current.templateUrl, 'partials/one.html');
+ assert.equal($route.current.controller, 'TestOneController');
+ });
+
+});
+</code></pre>
+
+<ol>
+<li>When the route is loaded, the <code>current</code> property is updated. We then test to ensure that the current controller and template are <code>TestOneController</code> and <code>partials/one.html</code>, respectively.</li>
+<li>Did you notice that we wrapped the route change inside the <code>$apply</code> callback? Since unit tests don’t run the full Angular app, we had to simulate it by triggering the <a href="https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest">digest cycle</a>.</li>
+<li>Curious about <code>WhenGET</code>? Check out the <a href="https://docs.angularjs.org/api/ngMock/service/$httpBackend">Angular documentation</a>. Take note of <code>ExpectGET</code> as well. Can you re-write the above test to use <code>ExpectGET</code>?</li>
+</ol>
+
+
+<p>Make sure to run the tests one last time:</p>
+
+<pre><code class="sh">$ gulp unit
+[05:20:07] Using gulpfile ~/angular-testing-tutorial/Gulpfile.js
+[05:20:07] Starting 'unit'...
+INFO [karma]: Karma v0.12.31 server started at http://localhost:9876/
+INFO [launcher]: Starting browser Chrome
+INFO [Chrome 41.0.2272 (Mac OS X 10.10.2)]: Connected on socket R5qQUcjswAbpcvMK6JKu with id 67365006
+Chrome 41.0.2272 (Mac OS X 10.10.2): Executed 12 of 12 SUCCESS (0.16 secs / 0.027 secs)
+[05:20:10] Finished 'unit' after 3.44 s
+</code></pre>
+
+<h2>Conclusion</h2>
+
+<p>That’s it for unit tests. In the next <a href="http://mherman.org/blog/2015/04/26/testing-angularjs-with-protractor-and-karma-part-2">part</a>, we’ll test the entire application, front to back, using end-to-end (E2E) tests via Protractor.</p>
+
+<p>Checkout the third tag, <code>v3</code>, to view all the completed unit tests:</p>
+
+<pre><code class="sh">$ git checkout tags/v3
+</code></pre>
+
+<p>Ready for more?</p>
+
+<p>Try adding some <a href="http://mherman.org/blog/2014/06/12/primer-on-angularjs-service-types/">Factories/Services</a> and Filters to your app to continue practicing. Since the syntax is relatively the same for testing all parts of an Angular app, you should be able to extend your testing knowledge to both factories and filters. Take a look at this <a href="https://github.com/mjhea0/thinkful-mentor/tree/master/angular/projects/angular-unit-test-demo/app/components">example</a> for help getting started. Once you feel comfortable with factories, controllers, and filters, move on to testing more difficult components, like directives, resources, and animations. Good luck!</p>
+
+<p>Comment below with questions.</p>
+]]></content>
+ </entry>
+
+ <entry>
+ <title type="html"><![CDATA[Kickstarting Angular With Gulp and Browserify, Part 2 - Browserify]]></title>
+ <link href="http://mherman.org/blog/2014/08/15/kickstarting-angular-with-gulp-and-browserify-part-2/"/>
+ <updated>2014-08-15T09:56:00-06:00</updated>
+ <id>http://mherman.org/blog/2014/08/15/kickstarting-angular-with-gulp-and-browserify-part-2</id>
+ <content type="html"><![CDATA[<p>Hello. Welcome to the second half. <a href="http://mherman.org/blog/2014/08/14/kickstarting-angular-with-gulp">Last time</a>, we built a nice Angular starter project, utilizing Gulp and Bower. Let’s take this a step further and add the power of <a href="http://browserify.org/">Browserify</a> into the mix. Before you read any further, check out the <a href="https://github.com/substack/browserify-handbook#introduction">Introduction</a> to the <a href="https://github.com/substack/browserify-handbook">Browserify Handbook</a> to learn about the problems that Browserify solves.</p>
+
+<blockquote><p>Just want the code? Get it <a href="https://github.com/mjhea0/angular-gulp-browserify-seed">here</a>.</p></blockquote>
+
+<h2>Install Dependencies</h2>
+
+<p>Let’s get Browserify installed…</p>
+
+<h3>First, install Browserify globally</h3>
+
+<pre><code class="sh">$ npm install -g browserify
+</code></pre>
+
+<h3>Then install the Gulp dependencies locally</h3>
+
+<pre><code class="sh">$ npm install gulp-browserify gulp-concat --save
+</code></pre>
+
+<p>The <a href="https://github.com/deepak1556/gulp-browserify">former</a> dependency allows you to run Browserify from Gulp, while the <a href="https://github.com/wearefractal/gulp-concat">latter</a> concatenates all the Bowerserify dependencies into a single JS file.</p>
+
+<h2>Update the Gulpfile</h2>
+
+<h3>Update the requirements</h3>
+
+<pre><code class="javascript">var browserify = require('gulp-browserify');
+var concat = require('gulp-concat');
+</code></pre>
+
+<h3>Add the following tasks</h3>
+
+<pre><code class="javascript">gulp.task('browserify', function() {
+ gulp.src(['app/js/main.js'])
+ .pipe(browserify({
+ insertGlobals: true,
+ debug: true
+ }))
+ .pipe(concat('bundled.js'))
+ .pipe(gulp.dest('./app/js'))
+});
+</code></pre>
+
+<h3>Now update the default task</h3>
+
+<pre><code class="javascript">// default task
+gulp.task('default',
+ ['lint', 'browserify', 'connect']
+);
+</code></pre>
+
+<h2>Update the HTML</h2>
+
+<p>Change the included JS file in <em>index.html</em>.</p>
+
+<p>From:</p>
+
+<p>``` html</p>
+
+<script src="./js/main.js"></script>
+
+
+<pre><code>
+To:
+</code></pre>
+
+<script src="./js/bundled.js"></script>
+
+
+<pre><code>
+### Test
+
+To recap:
+
+1. We added Browserify
+2. Updated the build process so that a single JS file named *bundled.js* is created
+3. Updated *index.html* to include that new JS file
+</code></pre>
+
+<p>$ gulp
As always, I’d love to hear your feedback. How are you using Browserify in your projects? Comment below.
Thanks for reading.
-]]>
-
-
-
-
-
- 2014-08-14T08:17:00-06:00
- http://mherman.org/blog/2014/08/14/kickstarting-angular-with-gulp
- Let’s develop an Angular boilerplate. Why? Despite the plethora of Angular seeds/generators/templates/boilerplates/starters/etc. on Github, none of them will ever do exactly what you want unless you build your own, piece by piece. By designing your own, you will better understand each component as well as how each fits into the greater project. Stop fighting against a boilerplate that just doesn’t fit your needs and start from scratch. Keep it simple, as you learn the process.
-
-
In this first part, we’ll start with Angular and Gulp, getting a working project setup. Next time we’ll add Browserify into the mix.
-
-
This tutorial assumes you have Node.js installed and have working knowledge of NPM and Angular. Just want the code? Get it here.
-
-
Project Setup
-
-
Install Dependencies
-
-
Setup a project folder and create a package.json file:
-
-
$ mkdir project_name && cd project_name
-$ npm init
-
-
-
The npm init command helps you create your project’s base configuration through an interactive prompt. Be sure to update the ‘entry point’ to ‘gulpfile.js’. You can just accept the defaults on the remaining prompts.
-
-
Do the same for Bower:
-
-
$ bower init
-
-
-
Accept all the defaults. After the file is created update the ignore list:
You can specify where you want the dependencies (commonly known as bower components) installed to by adding a .bowerrc file and adding the following code:
The --save flag adds the dependencies to the package.json and bower.json files, respectively.
-
-
We’ll address each of these dependencies shortly. For now, be sure you understand the project’s core dependencies:
-
-
-
Gulp is a Javascript task runner, used to automate repetitive tasks (i.e., minifying, linting, testing, building, compiling) to simplify the build process.
Again, this should be relatively straightforward. We setup the basic Angular code to establish a route handler along with a controller that passes the variable test to the template.
-
-
partial1.html
-
-
Now let’s add the partial template:
-
-
1
-2
-3
-4
-5
-6
-
</p>
-
-<p>{{ test }}</p>
-
-
-<p>
-
-
-
Test
-
-
Back in your browser, refresh the page. You should see the text:
-
-
Angular-Gulp-Browserify-Starter
-
-Testing...
-
-
-
Create the Build
-
-
Now that our app is working locally, let’s modify our gulpfile.js to generate a deployable build. Kill the server.
The default task, gulp, is a compound task that runs both the lint and connect tasks. Again, this just serves the files in the “app” folder on http://localhost:8888/.
-
-
Build
-
-
The build task creates a new directory called “dist”, runs the linter, minifies the CSS and JS files, and copies all the HTML files and Bower Components. You can then see what the final build looks like on http://localhost:9999/ before deployment. You should also run the clean task before you generate a build.
-
-
Test this out:
-
-
$ gulp build
-
-
-
Conclusion
-
-
Well, hopefully you now have a better understanding of how Gulp can greatly simply the build process, handling a number of repetitive tasks. Next time we’ll clean up some of the mess that the Bower components leave behind by adding Browserify into the mix and detail a nice workflow that you can use for all your Angular projects.