Unit testing a directive that defines a controller in AngularJS - unit-testing

I'm trying to test a directive using Karma and Jasmine that does a couple of things. First being that it uses a templateUrl and second that it defines a controller. This may not be the correct terminology, but it creates a controller in its declaration. The Angular application is set up so that each unit is contained within its own module. For example, all directives are included within module app.directive, all controllers are contained within app.controller, and all services are contained within app.service etc.
To complicate things further, the controller being defined within this directive has a single dependency and it contains a function that makes an $http request to set a value on the $scope. I know that I can mock this dependency using $httpBackend mock to simulate the $http call and return the proper object to the call of this function. I've done this numerous times on the other unit tests that I've created, and have a pretty good grasp on this concept.
The code below is written in CoffeeScript.
Here is my directive:
angular.module('app.directive')
.directive 'exampleDirective', [() ->
restrict: 'A'
templateUrl: 'partials/view.html'
scope: true
controller: ['$scope', 'Service', ($scope, Service) ->
$scope.model = {}
$scope.model.value_one = 1
# Call the dependency
Service.getValue()
.success (data) ->
$scope.model.value_two = data
.error ->
$scope.model.value_two = 0
]
]
Here is the dependency service:
angular.module("app.service")
.factory 'Service', ['$http', ($http) ->
getValue: () ->
options.method = "GET"
options.url = "example/fetch"
$http _.defaults(options)
Here is the view:
<div>
{{model.value_one}} {{model.value_two}}
</div>
I've simplified this quite a bit, as my goal is only to understand how to wire this up, I can take it from there. The reason I'm structuring it this way is because I did not initially create this. I'm working on writing tests for an existing project and I don't have the ability to configure it any other way. I've made an attempt to write the test, but cannot get it to do what i want.
I want to test to see if the values are being bound to the view, and if possible to also test to see if the controller is creating the values properly.
Here is what I've got:
'use strict'
describe "the exampleDirective Directive", ->
beforeEach module("app.directive")
beforeEach module("app/partials/view.html")
ServiceMock = {
getValue : () ->
options.method = "GET"
options.url = "example/fetch"
$http _.defaults(options)
}
#use the mock instead of the service
beforeEach module ($provide) ->
$provide.value "Service", ServiceMock
return
$httpBackend = null
scope = null
elem = null
beforeEach inject ($compile, $rootScope, $injector) ->
# get httpBackend object
$httpBackend = $injector.get("$httpBackend")
$httpBackend.whenGET("example/fetch").respond(200, "it works")
#set up the scope
scope = $rootScope
#create and compile directive
elem = angular.element('<example-directive></example-directive>')
$compile(elem)(scope)
scope.$digest()
I don't know how close I am, or if this is even correct. I want to be able to assert that the values are bound to the view correctly. I've used Vojtajina's example to set up html2js in my karma.js file to allow me to grab the views. I've done a lot of research to find the answer, but I need some help. Hopefully a programmer wiser than I can point me in the right direction. Thank you.

Create the element in karma, then use the .controller() function with the name of your directive to grab the controller. For your example, replace the last couple of lines with these:
elem = angular.element('<div example-directive></div>');
$compile(elem)($rootScope);
var controller = elem.controller('exampleDirective');
Note, that given how you defined your directive, it should be by attribute, and not as an element. I'm also not 100% sure, but I don't think you need the scope.$digest; usually I just put anything that needs to be applied into a scope.$apply(function() {}) block.

Related

Dependency Variable Doesnt Exist

Newbie in coldbox so please have patience with me.
I am trying to implement TDD on my coldbox application.
Under my service model I inject this dependency.
property name="wirebox" inject="wirebox"
property name="populator" inject="wirebox:populator";
On my service model I have this method. GetallUsers()
User function new(){
return wirebox.getInstance("User");
}
function getAllUsers(){
var users= queryExecute(
"SELECT * FROM USERS",
{},
{returnType="array"}
).map(function(user){
return populator.populateFromStruct(new(),user);
});
return users;
}
And on my UserServiceTest I have this code:
component extends="coldbox.system.testing.BaseModelTest" model="models.UserService"{
/*********************************** LIFE CYCLE Methods ***********************************/
function beforeAll(){
super.beforeAll();
// setup the model
super.setup();
// init the model object
model.init();
}
function afterAll(){
super.afterAll();
}
/*********************************** BDD SUITES ***********************************/
function run(){
describe( "Users Suite", function(){
it( "can get list of users", function(){
var stubPopulator = stub().$( 'populateFromStruct', {} );
model.$property( 'populator', 'variables', stubPopulator );
var users= model.getAll();
expect( event.getPrivateValue( "users") ).toBeStruct();
});
});
}
But I got this error saying **variable [POPULATOR] doesn't exist**.
Hoping someone can help me.
You didn't show the full test bundle, but based on the name it would appear it is a unit test (or a ColdBox model test, which is a type of unit test). Unit tests do not spin up the ColdBox framework by default and do not process injections for the CFCs under test. They are created "naked" and it's up to you to provide mocks for an dependencies that CFC has.
So in this case, you'd need to provide a mock populator to your model to be used for the test. So something like this:
var stubPopulator = createStub().$( 'populateFromStruct', {} )
model.$property( 'populator', 'variables', stubPopulator )
var users= model.getAll();
My stubed populator just returns an empty struct. It's also worth noting I don't think your queryMap() is returning a struct like you think it is so you may need to confirm the functionality of that method.
Alternatively, you could switch to more of an integration test where you set this.loadColdBox to true in your test CFC's pseduo-constructor and then use getInstance( 'UserService' ) to get a fully built instance of your UserService which would have the populator injected into it. Exactly how this would look like depends on several things you haven't shared such as your test harness setup, and your test bundle CFC's base class.

Navigation Unit Testing in MvvmCross

Trying to unit test the navigation in one of my command calls into a private method. Just trying to test if the navigation request has been made as a result of this command execution.
There's the old documentation;
https://www.mvvmcross.com/documentation/fundamentals/testing
This documentation does not factor in new async based calls as far as I found; For example IMvxMainThreadAsyncDispatcher
Either we need to implement two ExecuteOnMainThreadAsync methods or inherit from MvxMainThreadAsyncDispatcher in MockDispatcher.
Also need to add IMvxMainThreadAsyncDispatcher in IoC registration.
var mockDispatcher = new MockDispatcher();
...
...
Ioc.RegisterSingleton<IMvxMainThreadAsyncDispatcher>(MockDispatcher);
So almost all tests work except navigation call requests. Below method inside MockDispatcher never gets called so I can't check request counts;
public async Task<bool> ShowViewModel(MvxViewModelRequest request)
{
Requests.Add(request);
return true;
}
Anybody has a working code that would mock and gets this Request called or in some other form? IMvxMainThreadDispatcher is being set as absolute, and navigation calls are not done with ShowViewModel<>() anymore in MVVMCross, it's done by calling navigationService.Navigate();
Well, I have found the solution to my question... The ShowViewModel is called when navigation service is properly mocked. I have found a piece of code on GitHub from MvvmCross's own repo on how they do tests for navigation. My next challenge would be to mock the destination viewModel but that's separate and below code doesn't cover that. Previously I had a very basic IMvxNavigationService mock.
var mockLocator = new Mock<IMvxViewModelLocator>();
mockLocator.Setup(
m => m.Load(It.IsAny<Type>(), It.IsAny<IMvxBundle>(), It.IsAny<IMvxBundle>(), It.IsAny<IMvxNavigateEventArgs>())).Returns(() => new FakeViewModel());
mockLocator.Setup(
m => m.Reload(It.IsAny<IMvxViewModel>(), It.IsAny<IMvxBundle>(), It.IsAny<IMvxBundle>(), It.IsAny<IMvxNavigateEventArgs>())).Returns(() => new FakeViewModel());
var mockCollection = new Mock<IMvxViewModelLocatorCollection>();
mockCollection.Setup(m => m.FindViewModelLocator(It.IsAny<MvxViewModelRequest>()))
.Returns(() => mockLocator.Object);
Ioc.RegisterSingleton(mockLocator.Object);
var loader = new MvxViewModelLoader(mockCollection.Object);
_navigationService = new MvxNavigationService(null, loader)
{
ViewDispatcher = MockDispatcher,
};
_navigationService.LoadRoutes(new[] { typeof(YourViewModelTestClass).Assembly });
Ioc.RegisterSingleton<IMvxNavigationService>(_navigationService);

Testing Ember (v1.0.0-rc.3) nested controllers using Mocha and Chai

I am trying to write test cases for controllers of an Ember (v1.0.0-rc.3) Application using Mocha and Chai. One of my controller is making use of another controller as follows
App.ABCController = Em.ObjectController.extend({
needs: ['application'],
welcomeMSG: function () {
return 'Hi, ' + this.get('controllers.application.name');
}.property(),
...
});
I wrote testCase as below:
describe 'ABCController', ->
expect = chai.expect
App = require '../support/setup'
abcController = null
before ->
App.reset()
ApplicationController = require 'controllers/application_controller'
ABCController = require 'controllers/abc_controller'
applicationController = ApplicationController.create()
abcController = ABCController.create()
describe '#welcomeMSG', ->
it 'should return Hi, \'user\'.', ->
msg = abcController.get('welcomeMSG')
expect(msg).to.be.equal('Hi, '+ applicationController.get('name'))
support/setup file is as follows
Em.testing = true
App = null
Em.run ->
App = Em.Application.create()
module.exports = App
Now whenever i try to run testcase i face error
"Before all" hook:
TypeError: Cannot call method 'has' of null
at verifyDependencies (http://localhost:3333/test/scripts/ember.js:27124:20)
at Ember.ControllerMixin.reopen.init (http://localhost:3333/test/scripts/ember.js:27141:9)
at superWrapper [as init] (http://localhost:3333/test/scripts/ember.js:1044:16)
at new Class (http://localhost:3333/test/scripts/ember.js:10632:15)
at Function.Mixin.create.create (http://localhost:3333/test/scripts/ember.js:10930:12)
at Function.Ember.ObjectProxy.reopenClass.create (http://localhost:3333/test/scripts/ember.js:11756:24)
at Function.superWrapper (http://localhost:3333/test/scripts/ember.js:1044:16)
at Context.eval (test/controllers/abc_controller_test.coffee:14:47)
at Hook.Runnable.run (test/vendor/scripts/mocha-1.8.2.js:4048:32)
at next (test/vendor/scripts/mocha-1.8.2.js:4298:10)
Please help me to resolve this issue. I will appreciate if someone provide me few links where i can study for latest ember.js application testing with mocha and chai.
Reading trough the docs I guess your problem is that your expect fails due to this to.be.equal. Try changing the assertion chain to this:
expect(msg).to.equal('Hi, '+ applicationController.get('name'))
Update
After reading your comment I guess the problem in your case is that when you do Ember.testing = true is equivalent to the before needed App.deferReadiness(), so it' obviously necessary to 'initialize' your App before using it, this is done with App.initialize() inside your global before hook. And lastly you call App.reset() inside your beforeEach hook's.
Please check also this blog post for more info on the update that introduced this change.
Hope it helps

Jasmine tests AngularJS Directives with templateUrl

I'm writing directive tests for AngularJS with Jasmine, and using templateUrl with them: https://gist.github.com/tanepiper/62bd10125e8408def5cc
However, when I run the test I get the error included in the gist:
Error: Unexpected request: GET views/currency-select.html
From what I've read in the docs I thought I was doing this correctly, but it doesn't seem so - what am I missing here?
Thanks
If you're using ngMockE2E or ngMock:
all HTTP requests are processed locally using rules you specify and none are passed to the server. Since templates are requested via HTTP, they too are processed locally. Since you did not specify anything to do when your app tries to connect to views/currency-select.html, it tells you it doesn't know how to handle it. You can easily tell ngMockE2E to pass along your template request:
$httpBackend.whenGET('views/currency-select.html').passThrough();
Remember that you can also use regular expressions in your routing paths to pass through all templates if you'd like.
The docs discuss this in more detail: http://docs.angularjs.org/api/ngMockE2E.$httpBackend
Otherwise use this:
You'll need to use the $injector to access the new backend. From the linked docs:
var $httpBackend;
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET('views/currency-select.html').respond(200, '');
}));
the Karma way is to load the template html dynamically into $templateCache. you could just use html2js karma pre-processor, as explained here
this boils down to adding templates '.html' to your files in the conf.js file
as well
preprocessors = {
'.html': 'html2js'
};
and use
beforeEach(module('..'));
beforeEach(module('...html', '...html'));
into your js testing file
If this is a unit-test, you won't have access to $httpBackend.passthrough(). That's only available in ngMock2E2, for end-to-end testing. I agree with the answers involving ng-html2js (used to be named html2js) but I would like to expand on them to provide a full solution here.
To render your directive, Angular uses $http.get() to fetch your template from templateUrl. Because this is unit-testing and angular-mocks is loaded, angular-mocks intercepts the call to $http.get() and give you the Unexpected request: GET error. You can try to find ways to by pass this, but it's much simpler to just use angular's $templateCache to preload your templates. This way, $http.get() won't even be an issue.
That's what the ng-html2js preprocessor do for you. To put it to work, first install it:
$ npm install karma-ng-html2js-preprocessor --save-dev
Then configure it by adding/updating the following fields in your karma.conf.js
{
files: [
//
// all your other files
//
//your htmp templates, assuming they're all under the templates dir
'templates/**/*.html'
],
preprocessors: {
//
// your other preprocessors
//
//
// tell karma to use the ng-html2js preprocessor
"templates/**/*.html": "ng-html2js"
},
ngHtml2JsPreprocessor: {
//
// Make up a module name to contain your templates.
// We will use this name in the jasmine test code.
// For advanced configs, see https://github.com/karma-runner/karma-ng-html2js-preprocessor
moduleName: 'test-templates',
}
}
Finally, in your test code, use the test-templates module that you've just created. Just add test-templates to the module call that you typically make in beforeEach, like this:
beforeEach(module('myapp', 'test-templates'));
It should be smooth sailing from here on out. For a more in depth look at this and other directive testing scenarios, check out this post
You could perhaps get the $templatecache from the injector and then do something like
$templateCache.put("views/currency-select.html","<div.....>");
where in place of <div.....> you would be putting your template.
After that you setup your directive and it should work just fine!
If this is still not working , use fiddler to see the content of the js file dynamically generated by htmltojs processor and check the path of template file.
It should be something like this
angular.module('app/templates/yourtemplate.html', []).run(function($templateCache) {
$templateCache.put('app/templates/yourtemplate.html',
In my case , it was not same as I had in my actual directive which was causing the issue.
Having the templateURL exactly same in all places got me through.
As requested, converting a comment to an answer.
For the people who want to make use of #Lior's answer in Yeoman apps:
Sometimes the way the templates are referenced in karma config and consequently - the names of modules produced by ng-html2js don't match the values specified as templateUrls in directive definitions.
You will need adjusting generated module names to match templateUrls.
These might be helpful:
https://github.com/karma-runner/karma-ng-html2js-preprocessor#configuration
gist: https://gist.github.com/vucalur/7238489
this is example how to test directive that use partial as a templateUrl
describe('with directive', function(){
var scope,
compile,
element;
beforeEach(module('myApp'));//myApp module
beforeEach(inject(function($rootScope, $compile, $templateCache){
scope = $rootScope.$new();
compile = $compile;
$templateCache.put('view/url.html',
'<ul><li>{{ foo }}</li>' +
'<li>{{ bar }}</li>' +
'<li>{{ baz }}</li>' +
'</ul>');
scope.template = {
url: 'view/url.html'
};
scope.foo = 'foo';
scope.bar = 'bar';
scope.baz = 'baz';
scope.$digest();
element = compile(angular.element(
'<section>' +
'<div ng-include="template.url" with="{foo : foo, bar : bar, baz : baz}"></div>' +
'<div ng-include="template.url" with=""></div>' +
'</section>'
))(scope);
scope.$digest();
}));
it('should copy scope parameters to ngInclude partial', function(){
var isolateScope = element.find('div').eq(0).scope();
expect(isolateScope.foo).toBeDefined();
expect(isolateScope.bar).toBeDefined();
expect(isolateScope.baz).toBeDefined();
})
});
If you are using the jasmine-maven-plugin together with RequireJS you can use the text plugin to load the template content into a variable and then put it in the template cache.
define(['angular', 'text!path/to/template.html', 'angular-route', 'angular-mocks'], function(ng, directiveTemplate) {
"use strict";
describe('Directive TestSuite', function () {
beforeEach(inject(function( $templateCache) {
$templateCache.put("path/to/template.html", directiveTemplate);
}));
});
});

AngularJS - basic testing with injection

So I'm new to the whole testing thing (I've been one of those people who has said 'I should write unit tests...' but never ended up ever doing it :-p).
I'm now writing unit tests for this project.  I'm using testacular + Jasmine, with browserify to compile things.  I was having no problems until I started trying to do a lot AngularJS injection-stuff.
Right now I'm simply trying to do a test of ng-model to get my head around all of it.
I have a testacular.conf file which includes everything necessary:
files = [
'../lib/jquery.js',
'../lib/angular.js',
'./lib/jasmine.js',
'./lib/angular-mocks.js',
JASMINE_ADAPTER,
'./tests.js' //compiled by browserify
];
I have my controller defined (MainCtrl.coffee)
MainCtrl = ($scope, $rootScope) ->
$scope.hello = 'initial'
module.exports = (angularModule) ->
angularModule.controller 'MainCtrl', ['$scope', '$rootScope', MainCtrl]
return MainCtrl
And I have my test itself: (_MainCtrlTest.coffee, in same directory as MainCtrl.coffee)
testModule = angular.module 'MainCtrlTest', []
MainCtrl = require('./MainCtrl')(testModule)
describe 'MainCtrlTest', ->
scope = null
elm = null
ctrl = null
beforeEach inject ($rootScope, $compile, $controller) ->
scope = $rootScope.$new()
ctrl = $controller MainCtrl, $scope: scope
elm = $compile('<input ng-model="hello"/>')(scope)
describe 'value $scope.hello', ->
it 'should initially equal input value', ->
expect(elm.val()).toBe scope.hello
it 'should change when input value changes', ->
scope.$apply -> elm.val('changedValue')
expect(scope.hello).toBe elm.val()
The test fails immediately, with the input's elm.val() returning blank, and scope.hello returning the intended value ('initial', set in MainCtrl.coffee)
What am I doing wrong here?
To get this working, you need to do scope.$apply():
it 'should initially equal input value', ->
scope.$apply()
expect(elm.val()).toBe scope.hello
Don't test the framework, test your code
Your test is trying to test whether Angular's binding, and ng-model works. You should rather trust the framework and test your code instead.
Your code is:
the controller (setting initial scope.hello value)
html templates (and all the binding, directives in there)
You can test the first one very easily, without even touching any DOM. That's the beauty of AngularJS - strong separation of view/logic.
In this controller, there is almost nothing to test, but the initial value:
it 'should init hello', ->
expect(scope.hello).toBe 'initial'
To test the second one (template + binding), you want to do an e2e test. You basically want to test, whether the template doesn't contain any typos in binding etc... So you wanna test the real template. If you inline a different html during the test, you are testing nothing but AngularJS.