6

I'm testing a sequence that polls a resource until a condition is met.

Book = $resource("/books/:id", {id: "@id"});

function poll(success) {
  Book.get({id:1}, function() {
    if (canStop) {
       success();
    } else {
       $timeout(poll, 1000);
    }

  });
};

The test below fails with Error: Unsatisfied requests: GET /user_workshops/1

describe('poll', function() {
  beforeEach(function() {
    $httpBackend.expectGET('/books/1').respond(200,{id:1});
    $httpBackend.expectGET('/books/1').respond(200,{id:1, newVal:1});

    poll(function() {
       successCalled = true;
    });

    $httpBackend.flush();
    $timeout.flush();

    canStop=true;

    $httpBackend.flush();
  });

  it('should call success when canStop is true', function() {
     expect(successCalled).toBe(true);
  });
});

I've tried rearranging test order to put the second expectGET just before the second httpBackend.flush() but then I get:

Error: Unexpected request: POST /books/1
No more request expected
ChrisJ
  • 2,486
  • 21
  • 40

2 Answers2

11

After an hour of hair pulling I realised that the httpBackend is very specific about the order that tests a called in - the expectation must be set not just before you call flush, but before the resource request is made, and when you call flush you must have made exactly and only the requests expected.

This means if you want to flush between sequential requests, the order of requests and expectations must be exactly:

$httpBackend.expectGET('...')
resource.get();
$httpBackend.flush()
$httpBackend.expectGET('...')
resource.get();
$httpBackend.flush()
...
etc

So in the case of the code above, it works if I change the ordering to:

describe('poll', function() {
  beforeEach(function() {
    $httpBackend.expectGET('/books/1').respond(200,{id:1});

    poll(function() {
       successCalled = true;
    });

    $httpBackend.flush();

    $httpBackend.expectGET('/books/1').respond(200,{id:1, newVal:1});

    $timeout.flush();
    canStop=true;

    $httpBackend.flush();
  });

  it('should call success when canStop is true', function() {
     expect(successCalled).toBe(true);
  });
});
ChrisJ
  • 2,486
  • 21
  • 40
  • If you have a directive within a directive in which both have a templateUrl, this also applies – Esaith Dec 13 '19 at 16:48
0

You can also rearm $httpBackend method on each call. Some kind like:

var deleteMethod = function () {
    $httpBackend.when('DELETE', /.*/gi).respond(function(method, url, data) {
        deleteMethod(); // <--- See here method is rearmed
        return [200, 'OK', {}];
    });
}
deleteMethod();
Statyan
  • 111
  • 1
  • 3