Тестване на вложени обещания с Jasmine

Това работи, когато стартирам потребителския интерфейс в браузъра, но винаги получавам нула за „d“ в моя метод validateAsync, който трябва да извика метода done, за да го върне обратно към метода за запазване. Не мога да разбера как да използвам andCallFake (необходим за шпиониране на теста за уникално име), но също така да го накарам да върне (jQuery), отложено за извикване. Надяваме се, че този код ще ви даде достатъчно контекст, за да видите какво се опитвам да постигна.

    validateAsync = function () {
        var d,
            isValid = true,
            isUnique = false;
            // validate that name and description are given
            if (layout.Name() === '') {
                toastr.warning('Layout name is required', 'Layout');
                isValid = false;
            }
             // validate that there are no other layouts of the same type with the same name
            d = uiDataService.GetIsLayoutNameUniqueAsync(layout.LayoutId(), layout.Name(), layout.LayoutTypeId())
                .done(function (isUniqueResult) {
                    isUnique = isUniqueResult.toLowerCase() === "true";
                    if (!isUnique) {
                        toastr.warning('Layout name ' + layout.Name() + ' must be unique. There is already a layout with this name.', 'Layout');
                    }
                    // this is always undefined in my Jasmine tests
                    d.done(isValid && isUnique);
                })
                .fail(function (response) {
                    mstar.AjaxService.CommonFailHandling(response.responseText);
                });
            return d;
    },
    save = function () {
        validateAsync()
            .done(function (isValidResult) {
                var isValid = isValidResult.toLowerCase() === "true";
                if (!isValid) {
                    return;
                }
                 // show a toastr notification on fail or success
                dataContext.SaveChanges(layout, uiDataService)
                    .done(function (layoutIdFromSave) {
                        toastr.success('The layout was saved. Refreshing...');
                    })
                    .fail(function () {
                        toastr.error('There was an error saving the layout.');
                    })
                    .always(function () {
                        // toastr.info('finished');
                    });
            })
            .fail(function () {
                throw new Error('There was an error validating before save');
            });
    };      

    // in uiDataService
     getIsLayoutNameUniqueAsync = function (layoutId, layoutName, layoutTypeId) {
        return ajaxService.AjaxGetJsonAsync(webServiceUrl + "GetIsLayoutNameUnique?layoutId=" + layoutId + "&layoutName=" + escape(layoutName) + "&layoutTypeId=" + layoutTypeId);
    },
    // in ajaxService
 ajaxGetJsonAsync = function (url, cache) {
            return $.ajax({
                type: "GET",
                url: url,
                dataType: "json",
                accepts: {
                    json: "application/json"
                },
                cache: cache === undefined ? false : cache
        });
    },
// in a beforeEach
var getIsLayoutNameUniquePromiseSpy = spyOn(mstar.dataService.UiDataService, "GetIsLayoutNameUniqueAsync")
    .andCallFake(function () {
        spyObj.called = true;
        // http://stackoverflow.com/questions/13148356/how-to-properly-unit-test-jquerys-ajax-promises-using-jasmine-and-or-sinon
        var d = $.Deferred();
        d.resolve('true');
        return d.promise();
    });
// and a test
it("should show a toastr", function () {
    // Act
    vm.GetLayout().Name('Test');
    vm.GetLayout().Description('Test');
    vm.Save();
    // Assert
    expect(toastr.success).toHaveBeenCalledWith('The layout was saved. Refreshing...');
});

person AlignedDev    schedule 08.04.2013    source източник
comment
Вие предавате булево значение на .done(), където очаква функция.   -  person Beetroot-Beetroot    schedule 09.04.2013
comment
@Beetroot-Beetroot the done() там трябва да задейства .done в метода за запазване. $.Deferred() има метод .resolve, но не мисля, че $.Deferred().promise() има метод .resolve.   -  person AlignedDev    schedule 09.04.2013
comment
Това е правилно, само Deferred има методи за промяна на състоянието, докато Promise, получено от Deferred, е потребителски обект, който (като самия Deferred) може да реагира на промени в състоянието. Но моля, не се спирайте на тази точка, защото всичко в validateAsync() и save() е от страна на потребителя - промените в състоянието се управляват в рамките на метода uiDataService.GetIsLayoutNameUniqueAsync(). Няма да се опитвам да коментирам Jasmine, освен да кажа, че изглежда напълно безсмислено да се пише и дебъгва код, за да се дебъгва друг код, който е от същия порядък на сложност или по-прост.   -  person Beetroot-Beetroot    schedule 09.04.2013
comment
Прочетохте ли отговора ми?   -  person Beetroot-Beetroot    schedule 09.04.2013
comment
Благодаря за отговорите ви, работих върху нещо друго и току-що се върнах към това. Причината, поради която използвам Jasmine, е да имам Behavior Driven Development Specification тестове, които гарантират, че това, което искам да се случи, продължава да се случва по-късно в разработката.   -  person AlignedDev    schedule 09.04.2013
comment
Съгласен, кой знае, може някой ден да ми потрябва Жасмин и с благодарност ще си спомня твоето обяснение. Междувременно ще държа главата си добре заровена в пясъка, където обикновено е :)   -  person Beetroot-Beetroot    schedule 09.04.2013


Отговори (1)


В съответствие, не знам много за Jasmine, но приемайки кода сам по себе си, е много по-лесно да се види какво се случва, ако е оголен до голи кости.

Силно опростено, validateAsync() в момента е структурирано по следния начин:

validateAsync = function () {
    ...
    var d = fn_that_returns_a_promise().done(function() {
        ...
        d.done(boolean);
    }).fail(function() {
        ...
    });
    return d;
};

което не може да е правилно, защото .done() не приема булев аргумент и, докато не мога да кажа, че определено е грешно, d.done() не е наистина подходящо в рамките на d.done() манипулатор (макар и може би при различни обстоятелства).

Предлагам ви да използвате .then() за филтриране на случая на успех (като по този начин предавате ново обещание, разрешено с вашата булева стойност), като същевременно запазите .fail() за случая на неуспех; като структура, както следва:

validateAsync = function () {
    ...
    return uiDataService.GetIsLayoutNameUniqueAsync(...).then(function(...) {
        ...
        return isValid && isUnique;
    }).fail(function(...) {
        ...
    });
};

Така save() може да бъде както следва:

save = function() {
    validateAsync().done(function(isValid) {
        //validation success
        if(!isValid) return;
        ...
    }.fail(function() {
        //validation failure
        ...
    });
};

Сега всичко, което трябва да направите, е да „съедините точките“ (т.е. да въведете отново собствените си изявления и т.н.) и да се надявате, че не съм направил грешки.

person Beetroot-Beetroot    schedule 08.04.2013
comment
Това беше пътят. Не разбрах, че можете да се върнете от then и си мислех, че трябва да разреша върнато обещание от метода fn_that_returns_a_promise. Благодаря ти за помощта! - person AlignedDev; 09.04.2013
comment
Интересното е, че мисля, че съм прав, като казвам, че в validateAsync(), двата метода .then() и .fail() са комутативни - с други думи, те могат да бъдат верижни във всеки ред; .then().fail() или .fail().then(). Това не е общоприето, само в този случай. - person Beetroot-Beetroot; 09.04.2013