In my angular tests I keep getting a Error: Unexpected request: GET 'some/rails/view.html'
I'm using konacha for testing, which uses mocha instead of jasmine. The project is based around a Rails app which is the reason for using konacha.
Here's a really simple sample test that checks if the controller is defined in the Angular app:
describe "ShowAccountCtrl", ->
beforeEach angular.mock.module('APP_NAME')
beforeEach inject(($rootScope, $controller) ->
#scope = $rootScope.$new()
#ctrl = $controller 'ShowAccountCtrl',
$scope: #scope
)
it "should be defined", ->
expect(#ctrl).to.not.be.undefined
I've seen some things about $httpBackend.when('GET', /\.html$/).passThrough(); but konacha doesn't seem to have a similar method to passThrough()
These issues always happen upon a $httpBackend.flush().
Has anyone conquered this problem before? Is there a way to ignore requests to rails templates so I can focus on testing functionality of the controllers?
This is because Konacha doesn't support any integration with Rails views. The solution is to load angular's $templateCache manually, similar to what you have to do when using templates with the asset pipeline. To make this work you will need to make your test have the ERB pre-processor (e.g. some_spec.js.coffee.erb).
beforeEach inject ($templateCache) ->
template = '/app/templates/view.html'
content = """
<%= IO.read(Rails.root.join('/app/templates/view.html')) %>
"""
$templateCache.put template, content
Related
I'm trying to compose some unit tests in Karma/Jasmine for a particular module in my project, destination-filters.
Module Decleration:
angular.module('destination-filter', ['ngSanitize']);
My tests fail unless I remove ngSanitize as a dependency. To my understanding that is because when the module is instantiated it will try and pull in that dependency but because in my spec.js file I haven't declared that module it is failing.
Spec File:
describe('Destination Filter Controller', function () {
// Set the variables
var $controller;
var mockNgSanitize;
beforeEach(module('destination-filter'));
beforeEach(function() {
module(function($provide) {
$provide.value('ngSanitize', mockNgSanitize);
});
});
beforeEach(inject(function (_$controller_) {
$controller = _$controller_('DestinationFilterController');
}));
it('should expect the controller to not be null', function() {
// Check the controller is set
expect($controller).not.toBeNull();
});
});
Previously, when mocking out services or functions, the $provide method has proven very useful but I'm not sure my use of it is correct here. I'm assuming $provide used in this way can't mock entire modules but rather services?
To clarify, if I remove the ...['ngSantize'])... from my module deceleration the tests instantiate correctly. The error I am receiving with it left in is Error: [$injector:modulerr] destination-filter
There are three options you could take for using ngSanitize in your tests:
inject the service into your test
stub a method call on ngSanitize
mock the entire ngSanitize service
The option you choose is really dependent on the use of ngSanitize in your working code (not your test code).
Whichever one you go for you need to make the service available in your test, there is no need for $provider (this covers option 1 and there is no need to do any more than this if you just want to make this available to your filter):
beforeEach(module('ngSanitize'));
beforeEach(inject(function(_ngSanitize_) { // the underscores are needed
mockNgSanitize = _ngSanitize_;
}));
Also, make sure that all js files are picked up and loaded by karma. You can define this in karma.conf.js by adding them to the files: property.
2. Stub a method on the service
I like stubs and find them very useful when writing tests. Your tests should only test one thing, in your case a filter. Stubs give you more control over your tests and allow you to isolate the thing under test.
Typically filters, controllers, anything call on lots of other things (services or factories like $http or ngSanitize).
Assuming that your filter is using ngSanitize's $sanitize to sanitize some html you could stub out that method to return sanitized html you have defined to test against your expectations:
// in a beforeEach
spyOn(mockNgSanitize, "$sanitize").and.returnValue('<some>sanitized<html>');
mockNgSanitized.$sanitize(yourDirtyHtml);
See the jasmine docs for more info.
You might have to play around with spying on the right service but this should work ok.
3. Mock the entire service
I don't think you want to go with this option because it will drive you insane figuring out what needs mocking plus mocks can create unrealistic expectations and also, not particularly useful for your use case. If you really want to have a go then something like the below is heading in the right direction (again see the jasmine docs)
beforeEach(function() {
mockNgSanitize = ('ngSanitize', ['linky', '$sanitize', '$sanitizeProvider'];
});
it('mocks the ngSanitize service', function() {
expect(mockNgSanitize.linky).toBeDefined();
});
NB: in all the code above make sure you continue to declare any variables up at the top of your describe block.
My unit tests are all failing with:
Error: Unexpected request: GET views/partials/listings.html`.
I've been reading through this SO question: Jasmine tests AngularJS Directives with templateUrl, but it appears that the discussion resolves this problem when using the ngMockE2E $httpBackend rather than the ngMock $httpBackend. I've tried to use the .passThrough() method that is mentioned in the accepted answer, but I get this error:
TypeError: '$httpBackend.whenGET('views/partials/listings.html').passThrough' is not a function`
It appears that the passThrough() method is only available on the ngMockE2E $httpBackend.
I wrote my test to mimic the phonecatApp XHR test from the AngularJS tutorial. This is the test that I'm working on:
(function() {
'use strict';
describe('Controller: ListingsCtrl', function() {
var $httpBackend, ListingsCtrl, scope;
$httpBackend = false;
ListingsCtrl = false;
scope = {};
beforeEach(module('app'));
beforeEach(inject(function(_$httpBackend_, $controller, $rootScope) {
$httpBackend = _$httpBackend_;
scope = $rootScope.$new();
ListingsCtrl = $controller('ListingsCtrl', {
$scope: scope
});
$httpBackend.expectGET('/api/listings/active').respond([
{
address: '123 Fake St'
}, {
address: '456 Other Ave'
}
]);
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should have the correct default search parameters', function() {
$httpBackend.flush();
expect(scope.beds).toBe('Any');
expect(scope.maxRent).toBe('None');
expect(scope.search.address).toBe('');
expect(scope.search.side).toBe('');
});
it('should have listings after loading them from the API', function() {
expect(scope.listings.length).toBe(0);
$httpBackend.flush();
expect(scope.listings.length).toBeGreaterThan(0);
});
});
}).call(this);
Both of the tests fail in all four browsers that I test (Opera, Safari, Firefox and Chrome) with the same error message.
I was under the impression that unit testing with karma only loaded the controller code, and therefore wouldn't attempt to load any views or templates. Am I mistaken?
OK, I think there is great deal of confusion here, let me untangle this. Before diving into technical details here are some desirable properties of unit tests:
We want our unit-tests to be fast. Blazing fast. The reason is that we want to run unit tests often, very often (you do TDD, right?).
Unit testing setup should be as straightforward as possible. The reason is that we want the whole process to be as easy as possible. Otherwise people will tend to skip testing and we don't want bugs in our code.
Assuming that we want our tests to be easy to set up running fast you should not be serving templates via HTTP while testing directives with templates. Doing so will make the overall setup more complex (you need to setup a WWW server to serve templates, get the paths "right" etc.) and will slow down tests (additional, async HTTP request + network traffic while running tests).
What you should be doing instead is to preload directives' templates into $templateCache. The easiest way of doing this is to use the karma-ng-html2js-preprocessor. This pre-processor can get HTML files, stringify them as JS and put into $templateCache. If you are looking for a complete project setup with this approach, you can find an excellent example here: https://github.com/vojtajina/ng-directive-testing
To sum up: don't mix unit-testing and e2e mocks in your testing setup. This makes it more complex and slower to run. Preload directive templates into $templateCache instead avoiding any interactions with $http altogether.
I'm trying to run a function and have services injected into it. I thought this could easily be accomplished using $injector. So I tried the following (simplified example):
angular.injector().invoke( [ "$q", function( $q ) { $q.something(); } ] );
Which results in Uncaught Error: [$injector:unpr] Unknown provider: $qProvider <- $q.
I know I can solve that by using angular.injector( ["ng"] ) instead, but there are actually several more dependencies.
It would be perfectly fine, if I could just retrieve the injector instance that is used everywhere else in my application.
The documentation for angular.injector suggests that you can retrieve it with angular.element(document).injector(), but that results in undefined for me.
You shouldn't be needing this, but you can get your app's $injector using the root-element of your app (or any child element).
E.g., if you use ngApp on the body:
angular.element(document.body).injector();
ExpertSystem's answer worked perfectly for me
This works perfectly for me. I'm trying to invoke a function from an angular service outside the angular scope in the document "resume" event for use in my cordova application. Here is the code that I used
var injector = angular.element(document.body).injector(); //get the document
injector.invoke(['myService', function (myService) {
myService.doSomething();
}]);
Try this:
var $injector = angular.injector(['myApp','ng'])
For $location you need to bootstrap the app to the page (start the app):
var $injector = angular.bootstrap(document, ['myApp'])
This will return the injector every time you call it but won't create any conflicts if the script was already loaded.
Although I believe I'm following the instructions here for setting up $httpBackend to pass selected requests to the server, it's not working for me.
Here is a Plunkr with a failing test that shows what I'm doing and explains in comments what seems to be going wrong.
My spelunking suggests that, for some reason, the mock $httpBackend doesn't have an inner copy of the real $httpBackend so that, when it comes time to pass through the XHR request, it passes it to the mock $httpBackend instead. That second call throws an exception because it doesn't know what to do with the request.
Response to dtabuenc
I remember with appreciation your post on midway testing. You identify an important range of integration testing that falls between unit- and E2E-testing. I am standing on that middle ground.
I don't think you are being snarky at all. Your answer is perfect reasonable ... or it would be reasonable if it weren't contradicted by the text of the "API reference / ngMockE2E / $httpBackend". I quote:
This implementation can be used to respond with static or dynamic responses via the when api and its shortcuts (whenGET, whenPOST, etc) and optionally pass through requests to the real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch templates from a webserver) ...
[I]n an end-to-end testing scenario or in a scenario when an application is being developed with the real backend api replaced with a mock, it is often desirable for certain category of requests to bypass the mock and issue a real http request .... To configure the backend with this behavior use the passThrough request handler of when instead of respond.[emphasis mine].
The documentation is silent on the matter of E2E $httpBackend usage within a Jasmine environment. I can't think of a reason to preclude it. If there is such a reason they should state it clearly. Seriously, who reads about a mock component and doesn't anticipate using it in a test environment?
To "pass through requests to the real $httpBackend for specific requests, e.g. to interact with certain remote apis" is precisely what I intend to do. What could they possibly mean by "real $httpBackend" except the non-mock version of that component?
I do not understand your claim that
The ngMocksE2E module is designed to be used on the "server" side of things where the actual angular application is executing.
The word "server" appears exactly 3 times on that page, not once suggesting that any application code would be executed on a "server". I don't know what you mean by the "actual angular application" executing on "the 'server' side of things."
The documentation is perfectly clear that the E2E $httpBackend is not limited to E2E testing. It is also for "a scenario when an application is being developed with the real backend api replaced with a mock".
That's just a half step away from my scenario in which an application is being tested with the real backend api."
In my scenarios, the SUT is calling upon a component which fetches data from a server. My tests exist to verify that this dependent component succeeds in making such requests of the real backend and will retrieve or save data in the expected manner. This is an integration test that cannot be adequately satisfied by mocking the behavior of the backend.
Of course I can test (and do test) with mock XHR responses the component's ability to respond properly to what I predict will be the backend's behavior. That is not the same as validating that the component responds appropriately to the actual backend's behavior ... which might change as the application evolves and depart from the mocked responses in some significant way.
I would consider using your midway tester for this purpose if I understood how to swap it into the SUT's code path. I don't. I think the component making XHR requests is inaccessible to your ngMidwayTester. But I do know how to jam a real XHR helper into the pipeline if I have to.
Here is where I stand at the moment.
Either someone can show how to make $httpBackend pass certain requests through to the server - as the documentation proclaims it can - or I will replace the passThrough implementation myself with a working XHR implementation.
I prefer the first option. If driven to the second, I shall offer a link to it here for the benefit of others who share my needs and my interpretation of the API documentation.
Is there a 3rd way I'm missing?
I stumbled on the same problem but instead of implementing a rich API or replacing the original angular-mocks simply added in the following helper:
angular.module('httpReal', ['ng'])
.config(['$provide', function($provide) {
$provide.decorator('$httpBackend', function() {
return angular.injector(['ng']).get('$httpBackend');
});
}])
.service('httpReal', ['$rootScope', function($rootScope) {
this.submit = function() {
$rootScope.$digest();
};
}]);
It patches two issues that prevent an HTTP request from getting passed through:
Restores original $httpBackend;
Provides a method to fulfill the requests, as otherwise they would be in AngularJS' queue waiting for a digest loop.
describe('my service', function() {
var myService, httpReal;
beforeEach(module('myModule', 'httpReal'));
beforeEach(inject(function( _myService_, _httpReal_ ) {
myService = _myService_;
httpReal = _httpReal_;
}));
it('should return valid data', function(done) {
myService.remoteCall().then(
function(data) {
expect(data).toBeDefined();
done();
}, function(error) {
expect(false).toBeTruthy();
done();
});
httpReal.submit();
});
});
The following is an explanation of the purpose of the $httpBackend that is in the ngMockE2E module.
The ngMockE2E module is simply not designed nor intended to be used from within a jasmine specification.
When doing end-to-end testing there are two sides to the test. One is the angular application that is being tested, the other is the angular-scenario code that lives in the Jasmine Specification.
Under E2E tests there are no angular module, or ng-mocks, or anything angular-related on the jasmine side of things (other than the scenario runner).
The ngMocksE2E module is designed to be used on the "server" side of things where the actual angular application is executing. It's main purpose is to enable us to pre-can responses so that integration-level UI testing can proceed much quicker than if each page actually went to the server for JSON.
When using jasmine along with ng-mocks, angular will always replace the $httpBackend with the mock backend. When adding the ngMocksE2E module it will not be able to get ahold of any "real" $httpBackend and as you have already found out, will just wrap the mock and delegate to it on the pass-through.
It would seem that the kind of test you are trying to write is a test that doesn't test the UI integration, but tests the application javascript and server integration.
This is perfectly legitimate style of testing (referred to some as 'midwayTesting' in the angular community). Your problem is you are using the wrong tool.
I would take a look at this:
https://github.com/yearofmoo/ngMidwayTester
Which you would use instead of angular-mocks and angular.module() in order to facilitate the kind of testing I'm assuming you want to do.
You can read more about it here:
http://www.yearofmoo.com/2013/01/full-spectrum-testing-with-angularjs-and-karma.html
(apologies if you have already been linked there)
EDIT: (To address additional comments in question)
You have a real beef in that the documentation is not clear that ngMockE2E cannot be used on the client (i.e. karma/jasmine) side of an end-to-end testing setup. It is not unreasonable to interpret things like you have interpreted them, but it doesn't change the fact that the interpretation is wrong.
The ngMockE2E will pass through requests if instructed when used on the server side of an application rather than on the client side. This is meant so that you can still pass through certain requests that are hard to mock as pre-canned responses. What I mean by client and server-side is that in end-to-end testing there are two ends. You have the application to be tested which is served by a standard application server, and you have the test code that is driving the application usually executing in Karma or another test runner, which uses standard HTTP requests to communicate to the application which is executing in another process.
If you look at the documentation and how to setup ngMockE2E you will notice there is no mention of Jasmine, and the instructions are for how to set up in a real angular application:
myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
myAppDev.run(function($httpBackend) {
phones = [{name: 'phone1'}, {name: 'phone2'}];
// returns the current list of phones
$httpBackend.whenGET('/phones').respond(phones);
// adds a new phone to the phones array
$httpBackend.whenPOST('/phones').respond(function(method, url, data) {
phones.push(angular.fromJson(data));
});
$httpBackend.whenGET(/^\/templates\//).passThrough();
//...
});
As you can see in this example, they are mocking all the JSON data instructions, while letting it still fetch the templates from the server.
In order to use it from jasmine the setup would be quite different, using angular.mock.module('ngMockE2E') and then setting up the $httpBackend.whenGET() in a beforeEach() rather than in a module.run().
As far as ngMidwayTester I linked you to, I believe this would, in fact, be compatible with ngMockE2E. Essentially ngMidwayTester replaces angular.mock.module() and inject() with it's own implementations. So you could use it like this:
beforeEach(function(){
tester = ngMidwayTester('app', 'ngMockE2E');
$http = tester.inject('$http');
$httpBackend = tester.inject('$httpBackend');
$rootScope = tester.inject('$rootScope');
});
This should work, because you are no longer using the ngMock module (which always gets included when you use angular.mock.module()). Things should work exactly like you want them to using ngMidwayTester.
To test my app with real backend calls I used a modified version of angular-mocks
It works just like for unit-tests in Jasmine.
I'm using it with Jasmine 2.0, so a test looks like following :
it(' myTest', function (done) {
_myService.apiCall()
.then(function () {
expect(true).toBeTruthy();
done()
});
});
NB: the done is needed because of the async call.
Here is a solution that I use to make real HTTP calls when I'm using ngMock for unit tests. I mainly use it for debugging, trying out the API, getting JSON examples etc.
I wrote a more detailed post about the solution on my blog: How to Unit Test with real HTTP calls using ngMockE2E & passThrough.
The solution is as follows:
angular.mock.http = {};
angular.mock.http.init = function() {
angular.module('ngMock', ['ng', 'ngMockE2E']).provider({
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
$log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
$rootElement: angular.mock.$RootElementProvider
}).config(['$provide', function($provide) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
$provide.decorator('$controller', angular.mock.$ControllerDecorator);
}]);
};
angular.mock.http.reset = function() {
angular.module('ngMock', ['ng']).provider({
$browser: angular.mock.$BrowserProvider,
$exceptionHandler: angular.mock.$ExceptionHandlerProvider,
$log: angular.mock.$LogProvider,
$interval: angular.mock.$IntervalProvider,
$httpBackend: angular.mock.$HttpBackendProvider,
$rootElement: angular.mock.$RootElementProvider
}).config(['$provide', function($provide) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
$provide.decorator('$controller', angular.mock.$ControllerDecorator);
}]);
};
Include this source file after ngMock, for example:
<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="angular-mocks.js"></script>
<!-- this would be the source code just provided -->
<script type="text/javascript" src="ngMockHttp.js"></script>
How to write the test?
describe('http tests', function () {
beforeEach(module('moviesApp'));
var $controller;
var $httpBackend;
var $scope;
describe('real http tests', function() {
beforeEach(angular.mock.http.init);
afterEach(angular.mock.http.reset);
beforeEach(inject(function(_$controller_, _$httpBackend_) {
$controller = _$controller_;
$scope = {};
$httpBackend = _$httpBackend_;
// Note that this HTTP backend is ngMockE2E's, and will make a real HTTP request
$httpBackend.whenGET('http://www.omdbapi.com/?s=terminator').passThrough();
}));
it('should load default movies (with real http request)', function (done) {
var moviesController = $controller('MovieController', { $scope: $scope });
setTimeout(function() {
expect($scope.movies).not.toEqual([]);
done();
}, 1000);
});
});
});
How it works?
It uses ngMockE2E's version of $httpBackEndProvider, which provides us with the passThrough function we see being used in the test. This does as the name suggests and lets a native HTTP call pass through.
We need to re-define the ngMock module without its fake version of the $BrowserProvider, since that is what prevents the real HTTP calls in unit tests that use ngMock.
Why I do it this way?
I like the flexibility to being able to easily switch between using fakes and real HTTP calls as it helps my workflow when writing the tests, which is why ngMockE2E's version of $httpBackEndProvider is used. It also allows me to code the unit tests in the same way as using ngMock, where I can simply drop in/out the beforeEach/afterEach line to register angular.mock.http.init/reset.
Plunkr
Here's a Plunkr with an example.
I am working on a Rails 3.2 app that will be using AngularJS. I can get Angular to do what I need, but I am having a very difficult time figuring out how to test what I'm doing. I am using guard-jasmine to run Jasmine specs using PhantomJS.
Here is the (relevant) html:
<html id="ng-app" ng-app="app">
<div id="directive-element" class="directive-element">
</div>
</html>
The javascript (in coffeescript) looks like:
window.Project =
App: angular.module('app', [])
Directive: {}
Project.Directive.DirectiveElement =
->
restrict: 'C'
link: (scope, element, attrs) ->
element.html 'hello world'
Project.App.directive 'directiveElement', Project.Directive.DirectiveElement
The code above does exactly what it is intended to do. The tests are the problem. I can't get them to work at all. This is one thing I had tried. Posting this is mostly just to start the conversation somewhere.
describe 'App.Directive.DirectiveElement', ->
it 'updates directive-element', ->
inject ($compile, $rootScope) ->
element = $compile('<div id="app" ng-app="app"><div id="directive'element" class="directive-element"></div></div>')
expect(element.text()).toEqual('hello world')
As an aside, I am new to AngularJS, so if there are any best practices regarding namespacing, modules, etc. that I am not following, guidance would be appreciated.
How do I get a test for this to work?
Here's how alert directive is tested in angular-ui/bootstrap.
Here's another simple set of tests, for the buttons directive.
Here are a few tips:
Be sure to tell the test runner what module you are testing with beforeEach(module('myModule')).
If you have external templateUrls in your directives, you'll want to somehow pre-cache them for the test runner. The test runner can't asynchronously GET templates. In bootstrap, we inject the templates into the javascript with a build step, and make each template a module. We use grunt-html2js grunt task.
In your tests, use the inject helper in a beforeEach to inject $compile and $rootScope and any other services you'll need. Use var myScope = $rootScope.$new() to create a fresh scope for each test. You can do var myElement = $compile('<my-directive></my-directive>')(myScope); to create an instance of your directive, and have access to its element.
If a directive creates its own scope and you want to test against it, you can get access to that directive's scope by doing var directiveScope = myElement.children().scope() - It will get the element's child (the directive itself), and get the scope for that.
For testing timeouts, you can use $timeout.flush() to end all pending timeouts.
For testing promises, remember that when you resolve a promise, it will not call its then callbacks until the next digest. So in tests you have to do this a lot: deferred.resolve(); scope.$apply();.
You can find tests for directives of varying complexity in the bootstrap repo. Just look in src/{directiveName}/test/.
Angular Test Patterns may help you, there are examples in both coffeescript and javascript.
Here's a testing pattern to verify the example directive is rendering the expected output.