测试角度控制器而不模拟其依赖关系

时间:2016-03-21 18:37:00

标签: angularjs unit-testing jasmine

我知道整个互联网上有很多关于这个问题的信息(相信我,我花了整整2天时间搜索所有这些信息)。到目前为止,我发现的任何东西都无法帮助我。

这是我要测试的控制器:

(function () {
    "use strict";

    angular
        .module("productManagement")
        .controller("ProductListController", ["productResource", ProductListController]);

    function ProductListController(productResource) {
        var vm = this;

        productResource.query(function(data) {
            vm.products = data;
        });

        vm.showImage = false;

        vm.toggleImage = function () {
            vm.showImage = !vm.showImage;
        }
    }
})();

这是一个有效的茉莉花测试:

describe('Controller: ProductListController', function() {

    var ProductListController;

    beforeEach(function() {
        module('productManagement');     

        inject(function($controller) {
            ProductListController = $controller('ProductListController', {});  
        });
    });

    it ('showImage should be false', function() { 
        expect(ProductListController.showImage).toBe(false);
    });
});

一切都很好,但现在我想测试vm.products以确保其获得数据。

然而,下面的茉莉花测试失败了(这是我真正想测试的):

describe('Controller: ProductListController', function() {

    var ProductListController;

    beforeEach(function() {
        module('productManagement');     

        inject(function($controller) {
            ProductListController = $controller('ProductListController', {});  
        });
    });

    it ('products should be defined', function() { 
        expect(ProductListController.products).toBeDefined();
    });
});

错误是'期望定义未定义'。因此,由于某些原因,产品没有填充数据,但是在运行我的角度应用程序时通常可以正常工作。

所以现在你可能想知道“ productResource 里面发生了什么?” 好吧,你走了:

(function () {
    "use strict";

    angular
        .module("common.services")
        .factory("productResource", ["$resource", productResource]);

    function productResource($resource) {
        return $resource("/api/products/:productId");
    }

})();

我的角度应用实际上使用 $ httpBackend 来返回一些虚假的产品信息。因此,我不想在我的茉莉花测试中嘲笑这个,因为那就像嘲笑我的模拟。但是,从技术上讲这是一个单元测试,所以我应该在我的茉莉花测试中嘲笑httpBackend。好吧,我也试过了......

以下是我在测试中尝试模拟httpBackend的代码:

describe('Controller: ProductListController', function() {

    var ProductListController, httpBackend, $resource;

    beforeEach(function() {
        module('productManagement');    

        inject(function(_$httpBackend_, $controller, _$resource_) {
            httpBackend = _$httpBackend_;
            ProductListController = $controller('ProductListController', {});
            $resource = _$resource_;
        });
    });

    /*afterEach(function() {
        //httpBackend.flush();
        httpBackend.verifyNoOutstandingExpectation();
        httpBackend.verifyNoOutstandingRequest();
    });*/

    it ('products should be defined', function() {                

        var productsUrl = '/api/products/:productId';
        var products;

        httpBackend.whenGET(productsUrl).respond({
            'one': 1,
            'two': 2
        });

        httpBackend.expectGET(productsUrl);

        var rec = $resource("/api/products/:productId");

        rec.query(function(data) {
            products = data;
        });

        //httpBackend.flush();

        expect(products.one).toBe(1);
    });
});

我必须注释掉httpBackend.flush(),因为错误“没有待处理的请求才能刷新!”。由于其他错误,例如“未满足的请求”,我还必须注释掉afterEach函数。

即使这确实有效,我现在还没有测试我的控制器功能,而是我正在测试$ resource。所以这对我来说似乎不对。

有人可以告诉我我做错了什么吗?我宁愿只做第一种方法,但是在这一点上我会采取任何有效的方法!

2 个答案:

答案 0 :(得分:1)

$ httpBackend 是一个ngMock模块服务,您可以在测试代码中使用它来实际模拟 http响应(它有点模仿真实的后端,所以你可以检查您的应用程序如何对潜在的http调用作出反应)。另一方面,在生产代码中,您使用的是 $ resource 服务。在这个地方,它与$ httpBackend无关。

查看此代码

it ('products should be defined', function() {
    $httpBackend.expectGET('/api/products').respond({});
    $httpBackend.flush();
    expect(ProductListController.products).toBeDefined();
});

我们为您的测试添加了两行代码以模仿真实的后端,以便您的productResource将接收数据,就好像它是从后端提供的一样。

答案 1 :(得分:1)

所以我想出来,如果有其他人有这个问题。我现在可以测试在我的茉莉花测试中创建的模拟数据,以及测试我的服务发送的真实数据。

以下是代码演练:

这是我正在测试的角度控制器:

 // ProductListController.js

 (function () {
    "use strict";

    angular
        .module("productManagement")
        .controller("ProductListController", ["productResource", ProductListController]);

    function ProductListController(productResource) {
        var vm = this;

        productResource.query(function(data) {
            vm.products = data;
        });

        vm.showImage = false;

        vm.whatever = function() {
            return 'returning';
        }

        vm.toggleImage = function () {
            vm.showImage = !vm.showImage;
        }
    }
})();

这是茉莉花代码,它正确地对控制器进行单元测试,以验证我的控制器中的productResource.query()方法是否正确地将数据分配给`vm.products'变量。我将在茉莉花中嘲笑这些数据。

describe('Controller: ProductListController', function() {

    var ProductListController, $httpBackend;

    beforeEach(module('productManagement'));

    beforeEach(inject(function($controller, _$httpBackend_) {
        ProductListController = $controller('ProductListController', {});
        $httpBackend = _$httpBackend_;
    }));

    it ('products ', function() {
        $httpBackend.expectGET('/api/products').respond([{'productId': 13}]);
        $httpBackend.flush();
        expect(ProductListController.products[0].productId).toBe(13);
    });
});

$httpBackend.expectGET基本上是在等待某些东西 - 在这种情况下我的控制器 - 调用api然后用对象[{'productId': 13}]进行响应。这样可以适当地嘲弄我的后端,以便我们对控制器功能进行单元测试。

实际进行api调用的productResource代码是在我创建的工厂的幕后,并且正在我的控制器中使用。对于好奇的人来说,这里有 productResource 代码:

(function () {
    "use strict";

    angular
        .module("common.services")
        .factory("productResource", ["$resource", productResource]);

    function productResource($resource) {
        return $resource('/api/products/:productId');
    }

})();

现在,回答我原来的问题:在我的茉莉花测试中没有嘲笑http响应,如果我想测试并看看我们的后端发送给我们的控制器,我该怎么做?

嗯,完全免责声明,我没有使用"真实"后端。我实际上在我的实际角度应用程序中使用$ httpBackend来模拟真实的后端。这是因为我遵循了角度教程,这就是教程所做的。所以,我需要在该文件中添加一行代码。这是该文件的snippit和添加的代码行。

(function () {
"use strict";

var app = angular
            .module("productResourceMock", ["ngMockE2E"]);

app.run(function ($httpBackend) {
    var products =  [
        {
            "productId": 1,
            "productName": "Leaf Rake",
            "productCode": "GDN-0011",
            "releaseDate": "March 19, 2009",
            "description": "Leaf rake with 48-inch handle",
            "cost": 9.00,
            "price": 19.95,
            "category": "garden",
            "tags": ["leaf", "tool"],
            "imageUrl": "http://openclipart.org/image/300px/svg_to_png/26215/Anonymous_Leaf_Rake.png"
        },
        {
            "productId": 5,
            "productName": "Hammer",
            "productCode": "TBX-0048",
            "releaseDate": "May 21, 2013",
            "description": "Curved claw steel hammer",
            "cost": 1.00,
            "price": 8.99,
            "category": "toolbox",
            "tags": ["tool"],
            "imageUrl": "http://openclipart.org/image/300px/svg_to_png/73/rejon_Hammer.png"
        },
        {
            "productId": 2,
            "productName": "Garden Cart",
            "productCode": "GDN-0023",
            "releaseDate": "March 18, 2010",
            "description": "15 gallon capacity rolling garden cart",
            "cost": 20.00,
            "price": 32.99,
            "category": "garden",
            "tags": ["barrow", "cart", "wheelbarrow"],
            "imageUrl": "https://openclipart.org/image/300px/svg_to_png/58471/garden-cart.png"
        }
    ];

    var productUrl = "/api/products";
    $httpBackend.whenGET(productUrl).respond(products);

    // This is the line I added to make my jasmine test work properly
    $httpBackend.expectGET(productUrl).respond(products);

现在进行茉莉花测试。它与上面的方法相同,仅使用不同的it方法。所以这里只是it方法:

it ('products should really be defined', function() {
    $httpBackend.flush();
    expect(ProductListController.products[0].productId).toBe(1);
});

我首先冲洗后端,因为我的控制器已经ping了其余的api。然后我检查数据,果然,它包含我的模拟后端发送的内容。完成!

我想如果你想测试你真正的后端数据的正确性,茉莉花测试应该适合你。

很抱歉这个冗长的回答,但我想要彻底,以防有任何noobs(像我一样)谁有同样的问题。