New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ngMockE2E $httpBackend "passThrough()" doesn't work #1434

Closed
ghost opened this Issue Oct 2, 2012 · 68 comments

Comments

@ghost
Copy link

ghost commented Oct 2, 2012

Hi, I'm trying to set up some test that actually do hit the backend, but I can't get this to work:

$httpBackend.whenPOST(/./).passThrough()
$httpBackend.whenGET(/.
/).passThrough()

But I get the error: "POST /some/url No more request expected"

I am still able to use .whenGET().respond(), and it's variants, to mock the requests. Am I doing something wrong with passThrough()?

@mstaessen

This comment has been minimized.

Copy link
Contributor

mstaessen commented Feb 10, 2013

I too have this issue. As far as I can tell, the mocked $httpBackend keeps the "real" $httpBackend as a delegate but for some reason, this delegate is also a mocked $httpBackend, which does not expect the request resulting in this error.

@thescientist13

This comment has been minimized.

Copy link

thescientist13 commented Mar 6, 2013

Anyone got any feedback on this issue? I can't believe how hard it is to write an actual integration test in Angular... ugh.

@davidowens

This comment has been minimized.

Copy link

davidowens commented Mar 7, 2013

I found that this worked for me when I was having that problem on my views folder. Maybe it's a start for you too.

$httpBackend.whenGET(/views\/.*/).passThrough();

(from here https://groups.google.com/forum/?fromgroups=#!topic/angular/ObdxCoCObYU)

@tadas-subonis

This comment has been minimized.

Copy link

tadas-subonis commented May 26, 2013

And duplicate here: #2512

@megalithic

This comment has been minimized.

Copy link

megalithic commented Jul 8, 2013

I don't see where #2512 is actually a duplicate. There, the problem is failing to actually instantiate ngMockE2E for the httpbackend. Here, the problem is that the ngMockE2E delegates passThrough() requests to ngMock httpbackend (i.e. doesn't use a real http backend as the delegate)

@trav

This comment has been minimized.

Copy link

trav commented Aug 17, 2013

I am also having this issue.

@imthiyazph

This comment has been minimized.

Copy link

imthiyazph commented Aug 22, 2013

As a workaround, I modified angular-mocks.js and commented out the registration of $HttpBackendProvider in ngMock module. This way, the $httpBackend in ngMockE2E will use the actual $httpBackend in ng module when passThrough option is used.
In my specs where I was using ngMock.$httpBackend to mock the $http service, I added ngMockE2E as my module dependency to get my specs running.

@rozzerhmq

This comment has been minimized.

Copy link

rozzerhmq commented Nov 4, 2013

I am having this issue too. This issue is reported from a year ago and still there...

@rodrigopedra

This comment has been minimized.

Copy link

rodrigopedra commented Jan 25, 2014

I'm using version 1.2.9

If you are using ngAnimate and animating ngView, include the angular-animate.js after angular-mocks.js

this is my setup:

<script src="scripts/vendor/angular-1.2.9/angular.js"></script>
<script src="scripts/vendor/angular-1.2.9/angular-route.js"></script>
<script src="scripts/vendor/angular-1.2.9/angular-sanitize.js"></script>
<script src="scripts/vendor/angular-1.2.9/angular-resource.js"></script>
<script src="scripts/vendor/angular-1.2.9/angular-mocks.js"></script>
<script src="scripts/vendor/angular-1.2.9/angular-animate.js"></script>
<script src="scripts/app.js"></script>

and a sample app.js:

angular.module('app', ['ngRoute', 'ngAnimate', 'ngMockE2E'])
.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider) {
    'use strict';

    $routeProvider
        .when('/home', {
            controller: 'HomeCtrl',
            templateUrl: 'partials/home.html'
        })
        .otherwise( { redirectTo: '/home' } );

    $locationProvider.html5Mode(false).hashPrefix('!');
}])
.run(['$httpBackend',
    function ($httpBackend) {
        $httpBackend.whenGET(/\.html$/).passThrough();
    }
]);

I was having problems when including angular-animate.js before angular-mocks.js, I checked the network panel and the .html file was being downloaded correctly, then checked the elements panel and the DOM was being altered correctly too... changing the load order of the scripts worked for me.

@jeffbcross jeffbcross self-assigned this Feb 13, 2014

@jeffbcross jeffbcross added this to the Ice Box milestone Feb 13, 2014

@jeffbcross jeffbcross removed their assignment Feb 13, 2014

@schmod

This comment has been minimized.

Copy link
Contributor

schmod commented Feb 21, 2014

I too have this issue. As far as I can tell, the mocked $httpBackend keeps the "real" $httpBackend as a delegate but for some reason, this delegate is also a mocked $httpBackend, which does not expect the request resulting in this error.

I also noticed this when trying to debug this issue.

In a "real" browser, the ngMockE2E backend keeps the real $httpBackend around as its $delegate. However, when I'm running Jasmine/Karma, for some reason, that delegate isn't defined, and the "real" $httpBackend's constructor never seems to be called at all.

@schmod

This comment has been minimized.

Copy link
Contributor

schmod commented Feb 21, 2014

I some more digging, and this appears to be an artifact of the fact that the angular-mocks injector (window.inject) automatically adds ng and ngMock as module dependencies to anything that it injects.

Loading ngMock and ngMockE2E at the same time appears to the injector's providerCache cache, which ultimately leads to ngMockE2E's $httpBackend $delegate pointing to ngMock's $httpBackend, rather than ng's $httpBackend (which is what the documented functionality is).

If we manually inject ng's $httpBackend into ngMockE2E, it looks like the delegate gets set correctly...

If I make the following change to angular-mocks.js, .passThrough() works correctly in Jasmine, and "normal" browsers:

@@ -1761,7 +1761,9 @@ angular.module('ngMock', ['ng']).provider({
  * Currently there is only one mock present in this module -
  * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock.
  */
-angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) {
+angular.module('ngMockE2E', ['ng']).service('$httpBackend', function(){
+   return angular.injector(['ng']).get('$httpBackend');
+}).config(['$provide', function($provide) {
   $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
 }]);
@pkozlowski-opensource

This comment has been minimized.

Copy link
Member

pkozlowski-opensource commented Feb 21, 2014

@schmod why would you like to load both ngMock and ngMockE2E? Those are meant to be used for different tests types (unit vs. e2e). Mixing them sounds odd... What is your use-case?

@schmod

This comment has been minimized.

Copy link
Contributor

schmod commented Feb 21, 2014

I'm not trying to load both ngMock and ngMockE2E.

However, if I'm running Jasmine, and using the window.inject function, ngMock is provided for me, whether I want it or not:

window.inject = angular.mock.inject = function() {
  ...  
      modules.unshift('ngMock');
      modules.unshift('ng');

There are a few rare cases when I actually might want to use passThrough() in a unit test, which would require me to include ngMockE2E.

@pkozlowski-opensource

This comment has been minimized.

Copy link
Member

pkozlowski-opensource commented Feb 21, 2014

@schmod ok, so what is your actual issue / reproduce scenario?

@schmod

This comment has been minimized.

Copy link
Contributor

schmod commented Feb 21, 2014

I have a service in my application that loads/caches images (in IndexedDB as a blob).

When writing unit tests around this service, I would like to be able to have Karma serve up a "real" image for consumption by my service. For practical reasons, it's easier to just include a real image in my Karma.conf and use a whenGET().passThrough() instead of .respond(). I'd imagine that you'd want to do something similar anytime you'd ever want to consume large amounts of data in your unit tests (to avoid polluting the test specs with the actual data).

To use .passThrough(), I need to add module('ngMockE2E'); in my test's beforeEach block.

When doing this, I inadvertently end up instantiating both ngMock and ngMockE2E, which gives me a broken passThrough() function on $httpBackend.

Reproduce senario (inside of Jasmine/Karma/Chrome):

describe('passThrough', function(){
    var $http, $httpBackend, $rootScope;
    beforeEach(function(){
        module('ngMockE2E');
        inject(function(_$http_, _$httpBackend_, _$rootScope_){
            $http = _$http_;
            $httpBackend = _$httpBackend_;
            $rootScope = _$rootScope_;
        });
    });
    it('should do something', function(){
        $httpBackend.whenGET(/.*image.jpg$/).passThrough();
        $http.get('image.jpg');
        $rootScope.$apply();
    });
});

This throws the error:

Error: Unexpected request: GET image.jpg
No more request expected
    at $httpBackend (http://localhost:9050/base/app/scripts/angular-mocks.js:1207:9)
    at $httpBackend (http://localhost:9050/base/app/scripts/angular-mocks.js:1200:11)
    at sendReq (http://localhost:9050/base/app/bower_components/angular/angular.js:7819:9)
    at $http.serverRequest (http://localhost:9050/base/app/bower_components/angular/angular.js:7553:16)
    at wrappedCallback (http://localhost:9050/base/app/bower_components/angular/angular.js:10949:81)
    at wrappedCallback (http://localhost:9050/base/app/bower_components/angular/angular.js:10949:81)
    at http://localhost:9050/base/app/bower_components/angular/angular.js:11035:26
    at Scope.$eval (http://localhost:9050/base/app/bower_components/angular/angular.js:11955:28)
    at Scope.$digest (http://localhost:9050/base/app/bower_components/angular/angular.js:11781:31)
    at Scope.$apply (http://localhost:9050/base/app/bower_components/angular/angular.js:12061:24) 

Admittedly, this probably breaks the strict definition of what a "unit" test is, although there's also no good reason for this to be quite so broken...

@pkozlowski-opensource

This comment has been minimized.

Copy link
Member

pkozlowski-opensource commented Feb 21, 2014

@mstaessen unit test mocks and e2e mocks were never meant to be used together and I would suspect that there are many other issues with using both together.

Are you doing any actual assertions on the downloaded images?

@schmod

This comment has been minimized.

Copy link
Contributor

schmod commented Feb 21, 2014

$httpBackend is the only thing in the E2E mock, and we haven't had any other issues running with this configuration. I don't see any good reason why the two shouldn't be able to stack on top of each other.

(And, yes. We are doing assertions on the downloaded images. The service itself tries to determine the validity of the images that it downloads, which is why it's important that we should be able to send it real data during testing)

@pkozlowski-opensource

This comment has been minimized.

Copy link
Member

pkozlowski-opensource commented Feb 22, 2014

Similar issue being raised here:
#3385

@schmod

This comment has been minimized.

Copy link
Contributor

schmod commented Feb 24, 2014

(Updated my comment above to mention the actual error that my reproduce scenario throws.)

@ashclarke

This comment has been minimized.

Copy link

ashclarke commented Jun 2, 2014

I also have looked into this, because I have an expectGET and whenGET().passThrough to load a template file in a beforeEach.

On line 1165, it calls a $delegate function when definition.passThrough is true.

When this delegate (it calls the $httpBackend() again from line 1112) is called however, it seems to lose the expectation I gave it (probably due to expectations.shift() on line 1149 and it is therefore no longer set as wasExpected.

Is this the expected behaviour? I don't understand why it calls the mocked $httpBackend again when "passing through".

@ashclarke

This comment has been minimized.

Copy link

ashclarke commented Jun 3, 2014

As per schmod's experiences above, I tried commenting out line 1759: $httpBackend: angular.mock.$HttpBackendProvider as a part of the angular.module('ngMock', ['ng']).provider() initialisation and my tests ran as expected.

@diogobeda

This comment has been minimized.

Copy link

diogobeda commented Jun 9, 2014

Any updates on this? I'm having the same issue

@plap979

This comment has been minimized.

Copy link

plap979 commented Jun 24, 2014

I'm not using Karma but Jasmine test runner. However I had the same problem too. The $delegate is not the real $http service but the mock itself.

Anyway, as schmod and ashclarke, I commented that line too and it worked.

On the 1.2.18 the line is the 1742:

$httpBackend: angular.mock.$HttpBackendProvider, 
@23tux

This comment has been minimized.

Copy link

23tux commented Jul 7, 2014

+1

It would be handy to use passThrough() inside a unit test in some cases (e.g. I have to use a backend that I don't know (with rare documentation), and I'd like to write some unit tests, that should hit the backend. When everything works, I would mock them afterwards)

@IxDay

This comment has been minimized.

Copy link

IxDay commented Jul 10, 2014

Okay there is a workaround, but you will not be available to have both at the same time (unit test is expecting some call, e2e is just responding a fake or passThrough). Here is how to inject the $httpBackend from e2e in unit test

//be careful order is important
describe('inject e2e $httpBackend', function () {

  it('here the injection', function() {
    var $httpBackend;

    //instantiate your modules
    angular.mock.module('ngMockE2E', 'openstack.keystone.v2')

    angular.mock.module(function ($provide) {

      //retrieve the $httpBackend from module ng and override $delegate from ngMockE2E
      angular.injector(['ng'])
      .invoke(function($httpBackend) {
        $provide.value('$delegate', $httpBackend);
      });

      //retrieve the $httpBackend from module ng and override $delegate from ngMockE2E
      angular.injector(['ngMockE2E'])
      .invoke(['$httpBackend', function(_$httpBackend_){
        $httpBackend = _$httpBackend_;
      }]);

      $provide.value('$httpBackend', $httpBackend);
    });

   //and then it works
   $httpBackend.when('POST', '/identity/tokens').passThrough();
  });
});
@mathilde-pellerin

This comment has been minimized.

Copy link

mathilde-pellerin commented Oct 9, 2015

subscribing

@ethanmick

This comment has been minimized.

Copy link

ethanmick commented Oct 13, 2015

+1

1 similar comment
@tttomat19

This comment has been minimized.

Copy link

tttomat19 commented Oct 18, 2015

+1

schmod pushed a commit to schmod/angular.js that referenced this issue Oct 19, 2015

Andrew Schmadel
fix(ngMockE2E): allow $httpBackend.passThrough() to work when ngMock …
…is loaded

Allow $httpBackend.passThrough() to work normally when ngMock is loaded
concurrently with ngMockE2E, as is typically the case when writing tests with
angular.mock.module()

Fixes angular#1434

schmod pushed a commit to schmod/angular.js that referenced this issue Oct 19, 2015

Andrew Schmadel
fix(ngMockE2E): allow $httpBackend.passThrough() to work when ngMock …
…is loaded

Allow $httpBackend.passThrough() to work normally when ngMock is loaded
concurrently with ngMockE2E, as is typically the case when writing tests with
angular.mock.module()

Fixes angular#1434
@Kira2

This comment has been minimized.

Copy link

Kira2 commented Oct 28, 2015

+1
Used @bbraithwaite's solution (thank you) with the fix for Angular v1.4.3.

@hypery2k

This comment has been minimized.

Copy link

hypery2k commented Oct 29, 2015

+1

2 similar comments
@mmmichl

This comment has been minimized.

Copy link

mmmichl commented Nov 4, 2015

+1

@fabiooshiro

This comment has been minimized.

Copy link

fabiooshiro commented Nov 6, 2015

+1

@panmanphil

This comment has been minimized.

Copy link

panmanphil commented Nov 14, 2015

Adding to the chorus, +1. Here is the scenario. We have an isolated unit suite using Karma, jasmine and bard. Everything is mocked, that's fine. We'd also like an integration suite that does full or partial end to end tests, still using karma and jasmine, but not bard. It will make some http calls, say to our own api, but mock other calls, say to amazon s3 put urls or to api's that require too much setup to execute. Finally I'll have a non destructive smoke test using protractor.

This should be possible, and thanks to all who have worked some magic for workarounds.

@damiendube

This comment has been minimized.

Copy link

damiendube commented Nov 26, 2015

+1

1 similar comment
@EdoB

This comment has been minimized.

Copy link

EdoB commented Dec 7, 2015

+1

@Lunanne

This comment has been minimized.

Copy link

Lunanne commented Dec 23, 2015

+1 Trying to unit test a directive with templateUrl and running into this issue.

@mali-eg

This comment has been minimized.

Copy link

mali-eg commented Jan 1, 2016

+1 same issue here

@TraianAlex

This comment has been minimized.

Copy link

TraianAlex commented Jan 14, 2016

Error: [$injector:modulerr] Failed to instantiate module demoApp due to:
[$injector:modulerr] Failed to instantiate module ngMockE2E due to:
[$injector:nomod] Module 'ngMockE2E' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.4.8/$injector/nomod?p0=ngMockE2E
minErr/<@https://code.angularjs.org/1.4.8/angular.js:68:12
etc

@bartvanderwal

This comment has been minimized.

Copy link

bartvanderwal commented Jan 16, 2016

On a positive note: one comment in case this helps others. 😄

I was trying to mock the backend while testing the frontend logic in the browser (no unit test). I had some issues and reading this issue it seemed to confirm to me Angular's ngMockE2E did not support this scenario yet. However, upon double checking for me using a wildcard /.*/ regexp at the end of the mockrun this IS working (I'm using Angular 1.3):

angular.module("buyCoApp.services").run(mockRun);
mockRun.$inject = ["$httpBackend", "$http", "_"];
function mockRun($httpBackend, $http) {
    $httpBackend.whenGET("api/test").respond(function(method, url, data) {
     ...
     });
    $httpBackend.whenGET(/.*/).passThrough();
}

So for the api/test url my mock is used and the rest is passed to the server!

@igordeoliveirasa

This comment has been minimized.

Copy link

igordeoliveirasa commented Jan 18, 2016

+1

@atefth

This comment has been minimized.

Copy link

atefth commented Feb 11, 2016

Any solutions to this?

@schmod

This comment has been minimized.

Copy link
Contributor

schmod commented Feb 11, 2016

I've had a PR open for several months now. No clue what I need to do to get it reviewed or merged, but... it's there.

@gkalpak

This comment has been minimized.

Copy link
Member

gkalpak commented Feb 11, 2016

I guess there is no strong argument for the necessity to have both ngMock and ngMockE2E loaded together, yet. It doesn't seem like a good practice to mix unit and e2e test functionalities together.

I still think that unit tests should not interact with the server at all, but if enough people find this useful, I think the best way would be to leave ngMockE2E out of this and enable passing requests through in ngMock. It needs to be well thought through, though, and might not be trivial to get right.

Just my 2 cents...

@schmod

This comment has been minimized.

Copy link
Contributor

schmod commented Feb 11, 2016

The problem is that angular-mocks loads ngMock for you, regardless of whether you want it or not. I suspect that very few people are deliberately loading both, but any usage of angular.mock.inject automatically adds ngMock to the injected module, even if you've already loaded ngMockE2E (thus clobbering ngMockE2E's $httpBackend)

It's not hard to understand why somebody might want to write an integration test with Jasmine.

In my case, I want to test some functionality that loads and processes some very large files (which are sometimes binaries). I'd rather not have to mock these files by inlining them into my tests -- it's far cleaner to simply make a "real" HTTP request to fetch them when they're needed.

@mmmichl

This comment has been minimized.

Copy link

mmmichl commented Feb 12, 2016

I love to make service tests to assure the back-end services still behave as I expect. Too often the back-end guys changes something without telling me, which resulted in a tester standing next to me :)

@gkalpak: Including the pass through functionality directly in ngMock out of the box sounds like the finest solution. But I would already be happy not to need a crazy hack to make this work.

@gkalpak

This comment has been minimized.

Copy link
Member

gkalpak commented Feb 13, 2016

@schmod, if you have already loaded ngMockE2E you should not be running unit test. In which case, you should not call angular.mock.inject(). What do I miss ?

@alvarocantador

This comment has been minimized.

Copy link

alvarocantador commented Apr 21, 2016

+ 1

@teyc

This comment has been minimized.

Copy link

teyc commented Apr 29, 2016

Here's a working example of using ngMockE2E with $httpBackEnd

require('./test-helper.js');

// our system under test
// which is a wrapper around 
// Yahoo's weather service
//
function sunriseService($http, $log) {
  this.query = function(location) {

    var yql = 
      'select * from weather.forecast    \n\
       where woeid in (                  \n\
         select woeid from geo.places(1) \n\
         where text="' +                 
         location.replace("'", "''") + '")';

     var url = 'https://query.yahooapis.com/v1/public/yql' +
        '?q=' + yql.replace('    ', '') + 
        '&format=json' + 
        '&env=store://datatables.org/alltableswithkeys';

    $log.debug(url);

    return $http.get(url)
        .then(function(result) {
            return result.data.query.results.channel.item.condition;
        });
  }
}

angular.module('app', [])
  .service('sunriseService', function($http, $log) {
      return new sunriseService($http, $log);
   });

//
// test suite
//

// define a new module that takes a dependency on
// our module and the ngMockE2E module
//
//
// !! do not include 'ngMock' in the list
//    as it'll load ngMock and override 
//    $httpBackend
//
angular.module('appDev', ['app', 'ngMockE2E']);

describe('ngMockE2E', function() {

  beforeEach(function () {
    //
    // !! do not use ngMock's module function
    //    as it'll load ngMock and override 
    //    $httpBackend
    //
    angular.module('appDev')
        .run(function ($httpBackend) {
            $httpBackend.whenGET(/.*/).passThrough();
        });
    });

  it('uses $httpBackend passthrough', function (done) {

    //
    // !! do not use ngMock's inject function
    //    as it'll load ngMock and override 
    //    $httpBackend
    //
    $injector = angular.injector(['appDev']);
    $injector.invoke(function(sunriseService, $rootScope) {

      sunriseService.query('brisbane, australia')
        .then(function (forecast) {
            console.log(forecast);
            expect(Object.keys(forecast)).toContain('text');
            done();
        })
        .catch(function (error) {
            done.fail(JSON.stringify(error));
        });
        $rootScope.$digest();
    });
  });
});

petebacondarwin added a commit that referenced this issue Jun 6, 2016

fix(ngMockE2E): allow $httpBackend.passThrough() to work when ngMock …
…is loaded

Allow $httpBackend.passThrough() to work normally when ngMock is loaded
concurrently with ngMockE2E, as is typically the case when writing tests with
angular.mock.module()

Fixes #1434
Closes #13124
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment