Тестирование функций обратного вызова JavaScript с помощью jasmine

У меня есть следующие функции:

function getPersonData(id) {
  retrieveData(
    id,
    function(person) {
      if(person.name) {
        displayPerson(person);
      }
    }
}

function retrieveData(id, successCallBack) {
    executeRequest(id, {
      success: successCallBack
    });
}

getPersonData извлекает информацию о человеке на основе идентификатора. Он, в свою очередь, вызывает retrieveData, передавая идентификатор и функцию successCallBack.

retrieveData берет идентификатор и SuccessCallBack и вызывает другую функцию, executeRequest, которая получает данные и передает обратно объект человека.

Я пытаюсь протестировать getPersonData и настроить следующую спецификацию

describe("when getPersonData is called with the right person id", function() {
  beforeEach(function() {
    spyOn(projA.data, "retrieveData").and.returnValue(
      {
        'name': 'john'
      }
    );
    spyOn(projA.data, "displayPerson");
    projA.data.getPersonData("123");
  });

  it("displays the person's details", function() {
    expect(projA.data.displayPerson).toHaveBeenCalled();
  );
}); 

Но когда спецификация выполняется, метод displayPerson не вызывается. Это связано с тем, что данные о человеке, передаваемые обратно из успешного callBack function(person), не передаются, хотя я издевался над retrieveData, чтобы вернуть результат.

Мой вопрос: это правильный способ проверить функции обратного вызова? В любом случае, что я делаю неправильно?


person user12222    schedule 03.05.2016    source источник
comment
Я предполагаю, что использование returnValue для функции, которая не возвращает значение, не сработает. Есть ли альтернативы?   -  person user12222    schedule 03.05.2016
comment
Я решил перевести function(person) { if(person.name) { displayPerson(person); } } на новый метод, чтобы помочь с тестированием. Но я все же хотел бы знать, как можно протестировать приведенный выше код.   -  person user12222    schedule 03.05.2016


Ответы (1)


Итак, жасмин сложен во многих тонкостях, и я думаю, что есть две основные проблемы с вашим кодом.

  1. У вас слишком много асинхронных вызовов, завернутых друг в друга. Что само по себе не проблема, но делает тестирование в JASMINE чертовски сложным. Например, какой смысл иметь функцию retrieveData, которая просто вызывает функцию executeRequest с точно такими же параметрами, но немного другим способом.

Я переписал ваш getPersonData, чтобы он был таким

function getPersonData(id) {
  // this is needed to properly spy in Jasmine
  var self = this;

  //replaced retrieveData with just execute request
  // self is required to properly spy in Jasmine
  self.executeRequest(id, {
    success: function(person) {
     if (person.name) {
      self.displayPerson(person);
     }
    }
  })
}

//I don't know what exactly executeRequest does 
//but I took the liberty to just make it up for this example 
function executeRequest(id, callback) {
 callback.success({
   name: id
  });
}

//I also assumed that projA would look something like this
var projA = {
 data: {
  getPersonData: getPersonData,
  retrieveData: retrieveData,
  displayPerson: displayPerson,
  executeRequest: executeRequest
 }
};

2. Чтобы протестировать асинхронный код в Jasmine, вам необходимо включить в тест обратный вызов done. Кроме того, если вы ожидаете, что функция обратного вызова сработает автоматически, вам нужно настроить ее внутри функции setTimeout, иначе она никогда не сработает. Вот скорректированный пример:

    describe("when getPersonData is called with the right person id",  function() {
     beforeEach(function() {
      //you should not spyOn retriveData or executeCode because it will override the function you wrote and will never call displayPerson

      // you should only have spies on the methods you are testing otherwise they will override other methods
      spyOn(projA.data, "displayPerson");
     });

     it("displays the person's details", function(done) {
      // it's better to call the function within specific test blocks so you have control over the test
      projA.data.getPersonData("123");

      // at this point, it will just have called the getPersonData but will not call executeRequest
      setTimeout(function() {
       //this block will just call executeRequest
       setTimeout(function() {
        //this block will finally call displayPerson
        expect(projA.data.displayPerson).toHaveBeenCalled();

        //tell jasmine the test is done after this
        done();
        })
       })
     });
    })
person superjisan    schedule 03.05.2016