Тестирование вложенных обещаний с помощью Jasmine

Это работает, когда я запускаю пользовательский интерфейс в браузере, но я всегда получаю значение null для «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, заключается в том, что у меня есть тесты спецификации разработки, управляемой поведением, которые гарантируют, что то, что я хочу, произойдет позже в процессе разработки.   -  person AlignedDev    schedule 09.04.2013
comment
Выровнено, кто знает, может быть, однажды мне понадобится Жасмин, и я с благодарностью вспомню ваше объяснение. Между тем, я буду хорошо прятать голову в песок, где она обычно находится :)   -  person Beetroot-Beetroot    schedule 09.04.2013


Ответы (1)


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

Сильно упрощенный, 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