AngularJS/Karma/Jasmine - 服务调用未返回值

AngularJS/Karma/Jasmine - Service call not returning value

我正在尝试使用注入组件的服务调用 Github API - 是的,我正在使用 AngularJS 1.5.3。

在单元测试中,我没有收到返回的值(当我在浏览器中 运行 时,该函数确实有效)。我不确定我做错了什么,希望有人能给我指出正确的方向。

这是错误:

main.component.js

(function(){
    angular.module("app").component("mainComponent", {
        templateUrl: "/templates/main.component.html",
        controllerAs: "vm",
        controller: function(APIFactory, UserFactory, $state){
            const vm = this;

            vm.searchGithub = function(){
                APIFactory.getAPI(vm.searchText).then(function(res){
                    res.status !== 200 ? $state.go("404", {errorData: res.data }) : (
                        vm.User = new UserFactory.User(res.data),
                        $state.go("profile", {userData: vm.User})
                    );
                })
                .catch(function(err){
                    $state.go("fourOFour");
                });
            };
        }
    });
})();

main.component.spec.js

describe("Main Component", function(){
    var mainComponent, APIFactory, UserFactory, $httpBackend, $q, $state, $rootScope;

    const addy = "https://api.github.com/users/";

    beforeEach(angular.mock.module("app"));

    beforeEach(inject(function(_APIFactory_, _UserFactory_, _$httpBackend_, _$state_, _$q_, _$rootScope_, _$componentController_){
        APIFactory = _APIFactory_;
        UserFactory = _UserFactory_;
        $httpBackend = _$httpBackend_;
        $state = _$state_;
        $q = _$q_;
        $rootScope = _$rootScope_;
        $rootScope.$new();
        mainComponent = _$componentController_("mainComponent", { $scope : {} });
    }));

    describe("Checking if the searchGithub() worked correctly", function(){
        var result;

        beforeEach(function(){
            spyOn(mainComponent, "searchGithub").and.callThrough();
            spyOn(APIFactory, "getAPI").and.callThrough();
            result = {};
        });

        it("should make a call to UserFactory", function(){
            mainComponent.searchText = "someName";
            expect(mainComponent.searchText).toBeDefined();

            // RESPONSE_SUCCESS does exist, I've omitted it.
            $httpBackend.whenGET(addy + mainComponent.searchText).respond(200, $q.when(RESPONSE_SUCCESS));

            // This is where I expect something to work

            APIFactory.getAPI(mainComponent.searchText).then(function(res){
                result = res;
            });

            $httpBackend.flush();

            expect(APIFactory.getAPI).toHaveBeenCalledWith(mainComponent.searchText);
            expect(mainComponent.User).toBeDefined();
        });
    });


});

这就是我想出的解决方案。如果有人想给我更好的解决方案,我会提出想法。

首先我做了两个模拟,然后将它们注入 mainComponent,以及我模拟的 APIFactoryMock.getAPI 函数的间谍:

const APIFactoryMock = {
    getAPI: function(){}
};

const UserFactoryMock = {
    User: function(data){
        return {
            login: data.login,
            id: data.id,
            avatar_url: data.avatar_url,
            html_url: data.html_url,
            followers: data.followers,
            following: data.following,
            public_repos: data.public_repos,
            public_gists: data.public_gists,
            created_at: data.created_at,
            updated_at: data.updated_at,
            name: data.name,
            company: data.company,
            blog: data.blog,
            location: data.location,
            bio: data.bio,
            hireable: data.hireable,
            email: data.email,
            links: {
                followers_url: data.followers_url,
                following_url: data.following_url,
                subscriptions_url: data.subscriptions_url,
                repos_url: data.repos_url,
                organizations_url: data.organizations_url
            }
        }
    }
};

beforeEach(inject(function(_APIFactory_, _UserFactory_, _$httpBackend_, _$state_, _$q_, _$rootScope_, _$componentController_){
    APIFactory = _APIFactory_;
    UserFactory = _UserFactory_;
    $httpBackend = _$httpBackend_;
    $state = _$state_;
    $q = _$q_;
    $rootScope = _$rootScope_;
    $rootScope.$new();
    spyOn(APIFactoryMock, "getAPI").and.returnValue(RESPONSE_SUCCESS);
    bindings = { APIFactory: APIFactoryMock, UserFactory: UserFactoryMock, $state: $state };
    mainComponent = _$componentController_("mainComponent", { $scope : {} }, bindings);
}));

然后我为模拟编写了测试:

it("should make a call to UserFactory", function(){
        mainComponent.searchText = "someName";
        expect(mainComponent.searchText).toBeDefined();


        mainComponent.searchGithub(mainComponent.searchText);
        $httpBackend.whenGET(addy + mainComponent.searchText).respond(200, $q.when(RESPONSE_SUCCESS));

        $httpBackend.flush();

        mainComponent.User = UserFactoryMock.User(RESPONSE_SUCCESS.data);

        expect(mainComponent.searchGithub).toHaveBeenCalledWith(mainComponent.searchText);
        expect(mainComponent.User).toBeDefined();
        expect(mainComponent.User.id).toEqual(666);
    });

在上面的回答中,您在测试用例中手动调用 UserFactoryMock.User,这将创建一个用户对象。

但要正确测试功能,应该检查 UserFactory.User 在调用 APIFactory.getAPI 成功时调用(无需在测试用例中手动调用 UserFactory.User .

我建议将您的测试用例修改为如下所示:

describe("Main Component", function(){
var mainComponent, APIFactory, UserFactory, $httpBackend, $q, $state, $rootScope;

const addy = "https://api.github.com/users/";

beforeEach(angular.mock.module("app"));

beforeEach(inject(function(_APIFactory_, _UserFactory_, _$httpBackend_, _$state_, _$q_, _$rootScope_, _$componentController_){
    APIFactory = _APIFactory_;
    UserFactory = _UserFactory_;
    $httpBackend = _$httpBackend_;
    $state = _$state_;
    $q = _$q_;
    $rootScope = _$rootScope_;
    var scope = $rootScope.$new();
    var bindings = { APIFactory: APIFactory, UserFactory: UserFactory, $state: $state };
    mainComponent = _$componentController_("mainComponent", { $scope : scope }, bindings);
}));

describe("Checking if the searchGithub() worked correctly", function(){
    var result;

    beforeEach(function(){
        spyOn(mainComponent, "searchGithub").and.callThrough();
        spyOn(APIFactory, "getAPI").and.callFake(function() {
            var def = $q.defer();
            def.resolve(RESPONSE_SUCCESS);
            return def.promise;
        });
        spyOn(UserFactory, "User").and.callFake(function() {
            var user = { id: 666, .... };
            return user;
        });
    });

    it("should make a call to UserFactory", function(){
        mainComponent.searchText = "someName";
        $rootScope.$apply();
        expect(mainComponent.searchText).toBeDefined();

        mainComponent.searchGithub(); // Call the same way as it works in the code actually.

        $rootScope.$apply();

        //No manual call to 'UserFactory.User' or 'APIFactory.getAPI'. The call to 'APIFactory.getAPI' is resolved/succeeds, hence a call to 'UserFactory.User' is made and the same is tested
        expect(APIFactory.getAPI).toHaveBeenCalledWith(mainComponent.searchText);
        expect(UserFactory.User).toHaveBeenCalledWith(RESPONSE_SUCCESS.data);
        expect(mainComponent.User).toBeDefined();
        expect(mainComponent.User.id).toEqual(666);
    });
});


});