Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

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

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

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

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
Copy link
Contributor

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
Copy link

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
Copy link

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
Copy link

And duplicate here: #2512

@megalithic
Copy link

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
Copy link

trav commented Aug 17, 2013

I am also having this issue.

@imthiyazph
Copy link

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
Copy link

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

@rodrigopedra
Copy link

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
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
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
Copy link
Member

@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
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
Copy link
Member

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

@schmod
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
Copy link
Member

@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
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
Copy link
Member

Similar issue being raised here:
#3385

@schmod
Copy link
Contributor

schmod commented Feb 24, 2014

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

@ashclarke
Copy link

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
Copy link

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
Copy link

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

@plap979
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
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
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();
  });
});

@mpellerin42
Copy link

subscribing

@ethanmick
Copy link

+1

1 similar comment
@tttomat19
Copy link

+1

schmod pushed a commit to schmod/angular.js that referenced this issue Oct 19, 2015
…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
…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
Copy link

Kira2 commented Oct 28, 2015

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

@hypery2k
Copy link

+1

2 similar comments
@mmmichl
Copy link

mmmichl commented Nov 4, 2015

+1

@fabiooshiro
Copy link

+1

@panmanphil
Copy link

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
Copy link

+1

1 similar comment
@EdoB
Copy link

EdoB commented Dec 7, 2015

+1

@Lunanne
Copy link

Lunanne commented Dec 23, 2015

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

@mali-eg
Copy link

mali-eg commented Jan 1, 2016

+1 same issue here

@TraianAlex
Copy link

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
Copy link

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
Copy link

+1

@atefth
Copy link

atefth commented Feb 11, 2016

Any solutions to this?

@schmod
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
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
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
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
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 ?

@alvaroinckot
Copy link

alvaroinckot commented Apr 21, 2016

+ 1

@teyc
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 pushed a commit that referenced this issue Jun 6, 2016
…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 subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests