Тест жасмина с использованием .toHaveBeenCalledWith не подходит для регистрации

Одностраничное приложение, над которым я работаю, имеет вид входа в систему с двумя формами: форма входа и форма регистрации. Следующая спецификация описывает тесты для этих форм. Я использую Jasmine-jQuery 1.4.2.

// user_spec.js

describe("User", function() {

  var userController;

  beforeEach(function () {
    loadFixtures('menu.html');
    userController = new MyApp.User.Controller();
  });

  describe("LoginView", function() {

    beforeEach(function() {
      // Mock the $.ajax function to prevent XHR:
      spyOn($, "ajax").andCallFake(function(params) {});
    });

    it("should pass email and password with the 'signInForm:submit' event.", function() {
      var email = "[email protected]";
      var password = "Passw0rd";
      var callback = jasmine.createSpy("FormSubmitSpy");

      userController.loginView.$el.find("#signInEmail").val(email);
      userController.loginView.$el.find("#signInPassword").val(password);
      userController.loginView.bind("signInForm:submit", callback, this);
      userController.loginView.ui.signInForm.trigger("submit");

      expect(callback).toHaveBeenCalledWith({
        email: email,
        password: password
      });
    });

    it("should pass name, email and password with the 'signUpForm:submit' event.", function() {
      var name = "John Doe";
      var email = "[email protected]";
      var password = "Passw0rd";
      var callback = jasmine.createSpy("FormSubmitSpy");

      userController.loginView.$el.find("#signUpName").val(name);
      userController.loginView.$el.find("#signUpMail").val(email);
      userController.loginView.$el.find("#signUpPassword").val(password);
      userController.loginView.$el.find("#signUpPasswordConfirmation").val(password);
      userController.loginView.bind("signUpForm:submit", callback, this);

      userController.loginView.ui.signUpForm.trigger("submit");

      expect(callback).toHaveBeenCalledWith({
        name: name,
        email: email,
        password: password,
        password_confirmation: password
      });
    });

  });

});

Тест для формы входа успешно завершен, однако тест для формы регистрации завершается неудачно.

Error: Expected spy FormSubmitSpy to have been called with \
    [ { name : 'John Doe', email : '[email protected]', \
    password : 'Passw0rd', password_confirmation : 'Passw0rd' } ] \
    but it was never called.

    at new jasmine.ExpectationResult (http://localhost:3000/assets/jasmine.js?body=1:114:32)
    at null.toHaveBeenCalledWith (http://localhost:3000/assets/jasmine.js?body=1:1235:29)
    at null.<anonymous> (http://localhost:3000/assets/user_spec.js?body=1:233:24)
    at jasmine.Block.execute (http://localhost:3000/assets/jasmine.js?body=1:1064:17)
    at jasmine.Queue.next_ (http://localhost:3000/assets/jasmine.js?body=1:2096:31)
    at jasmine.Queue.start (http://localhost:3000/assets/jasmine.js?body=1:2049:8)
    at jasmine.Spec.execute (http://localhost:3000/assets/jasmine.js?body=1:2376:14)
    at jasmine.Queue.next_ (http://localhost:3000/assets/jasmine.js?body=1:2096:31)
    at jasmine.Queue.start (http://localhost:3000/assets/jasmine.js?body=1:2049:8)
    at jasmine.Suite.execute (http://localhost:3000/assets/jasmine.js?body=1:2521:14)

Использование форм в приложении не представляет проблемы. Данные передаются. Все работает нормально. Просто тест не работает.

Обход

Тест, однако, успешно, когда я задерживаю его выполнение.

_.defer(function() {
  expect(callback).toHaveBeenCalledWith({
    name: name,
    email: email,
    password: password,
    password_confirmation: password
  });
});

Почему эта работа и "нормальная" реализация не выполняются?


Вот упрощение данного случая:

it("should evaluate true", function() {
  var foo = false;
  _.defer(function() {
    foo = true;
  });
  expect(foo).toBeTruthy();
});

Ответ 1

Способ Жасмин, чтобы сделать то же самое без использования функции подчеркивания для отсрочки, будет следующим:

var flag = false;
...

runs(function() {
  userController.loginView.ui.signInForm.trigger("submit");
  setTimeout(function() { flag = true; }, 1);
}

waitsFor(function() {
  return flag;
}, "Blah this should never happen", 10);

runs(function() {
  expect(callback).toHaveBeenCalledWith({
    name: name,
    email: email,
    password: password,
    password_confirmation: password
  });
}

@Marc правильно, что проблема связана с использованием bind и тем, как Javascript отправляет события "иногда/обычно/всегда" в следующий цикл событий (как работает его асинхронный характер), поэтому, поскольку вы следите за обратным вызовом, вы хотите, чтобы ваши тесты были написаны для учета этого асинхронного поведения.

Когда ваши тесты написаны, вы рискуете, что первый тест не пройдет ни спорадически (я удивлен, что он работает так, как есть). Вы тестируете асинхронный обратный вызов события в неасинхронном режиме... смысл?

Ответ 2

Причина, по которой вы видите эту проблему, состоит в том, что обратный вызов вложен в какой-либо другой метод, возможно, привязка jQuery и jasmine spy обертывается непосредственно над вашим методом, поэтому, когда тест настроен, ваш метод не выполняется до следующего тика.

Я нашел эту страницу: http://trevmex.com/post/7017702464/nesting-spies-to-see-if-a-callback-in-a-deep-nest-has полезен, так как описывает вашу проблему и потенциальную работу.

Однако я обнаружил, что лучше не обязательно проверять обратные вызовы, поскольку их можно рассматривать как частные методы. Возможно, вы можете проверить его конечный результат?