44

This is a controller with a submit function:

$scope.submit = function(){   

 $http.post('/api/project', $scope.project)
      .success(function(data, status){
        $modalInstance.dismiss(true);
      })
      .error(function(data){
        console.log(data);
      })
  }
}

This is my test

it('should make a post to /api/project on submit and close the modal on success', function() {
    scope.submit();

    $httpBackend.expectPOST('/api/project').respond(200, 'test');

    $httpBackend.flush();

    expect(modalInstance.dismiss).toHaveBeenCalledWith(true);
  });

The error I get is:

Error: Unexpected request: GET views/appBar.html

views/appBar.html is my templateUrl:

 .state('project', {
    url: '/',
    templateUrl:'views/appBar.html',
    controller: 'ProjectsCtrl'
  })

So somehow ui-router is making my $httpBackend point to this instead of my submit function. I have the same issue in all my tests using $httpBackend.

Is there any solution to this?

1

5 Answers 5

48

Take this gist https://gist.github.com/wilsonwc/8358542

angular.module('stateMock',[]);
angular.module('stateMock').service("$state", function($q){
    this.expectedTransitions = [];
    this.transitionTo = function(stateName){
        if(this.expectedTransitions.length > 0){
            var expectedState = this.expectedTransitions.shift();
            if(expectedState !== stateName){
                throw Error("Expected transition to state: " + expectedState + " but transitioned to " + stateName );
            }
        }else{
            throw Error("No more transitions were expected! Tried to transition to "+ stateName );
        }
        console.log("Mock transition to: " + stateName);
        var deferred = $q.defer();
        var promise = deferred.promise;
        deferred.resolve();
        return promise;
    }
    this.go = this.transitionTo;
    this.expectTransitionTo = function(stateName){
        this.expectedTransitions.push(stateName);
    }

    this.ensureAllTransitionsHappened = function(){
        if(this.expectedTransitions.length > 0){
            throw Error("Not all transitions happened!");
        }
    }
});

Add it to a file called stateMock in your test/mock folder, include that file in your karma config if it isn't already picked up.

The setup before your test should then look something like this:

beforeEach(module('stateMock'));

// Initialize the controller and a mock scope
beforeEach(inject(function ($state //other vars as needed) {
    state = $state;
    //initialize other stuff
}

Then in your test you should add

state.expectTransitionTo('project');
Sign up to request clarification or add additional context in comments.

6 Comments

Awesome! I was almost skipping ui-router all together. Great work! You should share this work with the ui-router folks so people can find it better.
@user1572526 yeah Awesome! +1 rosswil just an huge headache ^^ it seems strange to me not to see just a lot of people here ....
Wow, this needs to be shared with ui-router, spent hours trying to determine why commenting out my $urlRouterProvider was fixing my tests. Thanks rosswil and for Whisher for pointing me here!!!
getting "TypeError: Cannot read property '$current' of undefined" with ui-router v0.2.11 (1937:19). The registerState method crashes, $state is undefined. Does anyone has the same issue ?
Can this be used to test that the correct $stateParams are passed on a transition using $state.go? ie: test that $state.go('users', {name: 'Bob' }) correctly passes {name: 'Bob'} as part of the transition?
|
39

This Github issue about Unit Testing UI Router explains more fully what's happening.

The problem is that $httpBackend.flush() triggers a broadcast which then triggers the otherwise case of the stateProvider.

A simple solution can be to do the following setup, as mentionned by @darinclark in Github thread mentionned above. This is valid if you do not need to test state transitions. Otherwise have a look to @rosswil's answer that is inspired by @Vratislav answer on Github.

beforeEach(module(function ($urlRouterProvider) {
    $urlRouterProvider.otherwise(function(){return false;});
}));

EDITED

Thanks to Chris T to report this in the comments, seems after v0.2.14? the best way to do this is to use

beforeEach(module(function($urlRouterProvider) {
  $urlRouterProvider.deferIntercept();
}));

6 Comments

UI-Router now has a $urlRouterProvider.deferIntercept() function. See github.com/angular-ui/ui-router/issues/…
This is the quickest solution
@ChrisT, your fix is the most elegant one. Thanx for posting it!
The problem is that $httpBackend.flush() triggers a broadcast which then triggers the otherwise case of the stateProvider. is exactly what is happening to most people, probably.
$urlRouterProvider is deprecated as of v1.0.0. Use $urlServiceProvider.deferIntercept(); instead.
|
3

If you don't want to add gist files like it says in the correct solution you can add a "when" condition to your $httpBackend to ignore GET petitions of views like this:

$httpBackend.when("GET", function (url) {
    // This condition works for my needs, but maybe you need to improve it
    return url.indexOf(".tpl.html") !== -1;
}).passThrough();

4 Comments

I got .passThrough is not a function error, not sure why. This worked for me: $httpBackend.whenGET(/templates\/.*/).respond('');
You can see here the documentation, passThrough is a valid function: docs.angularjs.org/api/ngMockE2E/service/$httpBackend Is a function from requestHandler that is the answer of "when" function.
Even I got .passThrough is not a function error. '.respond' worked fine though.
.passThrough is available in angular e2e tests (ngMockE2E), but not in unit tests (ngMock)
2

I have the same error your commented, after a call service they ask me about the url of otherwise ui-route.

To solve the problem of call the otherwise ui-route in testing is not inject $state in beforeach statment. In my testing $state not have sense to use it.

Comments

1

Move your services to their own module that have no dependency on ui.router. have your main app depend on this module. When you test don’t test the main app, test the module that has your services in it. The stateprovider won’t try to change state/route because this module knows nothing about the ui.router. This worked for me.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.