Reputation: 501
I know there is tons of information on this question all over the internet (trust me, I've spent 2 whole days searching through all of it). So far nothing I have found really helps me.
Here is the controller I want to test:
(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;
}
}
})();
Here is a jasmine test that works:
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);
});
});
All good, but now I want to test the vm.products to make sure its got data.
However, the below jasmine test fails ( and this is what I really want to test):
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();
});
});
The error is 'expects undefined to be defined'. So for some reason products is not getting populated with data, however when running my angular app normally this works fine.
So now you may be wondering "what is going on inside of productResource?" Well, here you go:
(function () {
"use strict";
angular
.module("common.services")
.factory("productResource", ["$resource", productResource]);
function productResource($resource) {
return $resource("/api/products/:productId");
}
})();
My angular app actually uses $httpBackend to return some bogus product info. Therefore I do not want to mock this in my jasmine test, since that would be like mocking my mock..right? However, technically this is a unit test, so I should be mocking httpBackend right in my jasmine test. Well I have tried that too....
Here is the code where I tried to mock httpBackend in my test:
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);
});
});
I have to comment out the httpBackend.flush() because of the error 'No pending request to flush !'. I also have to comment out the afterEach function because of other errors, such as 'unsatisfied requests'.
Even if this did work, I am now not testing my controller functionality, but rather I am testing $resource. So this just doesn't seem right to me.
Can someone please tell me what I am doing wrong? I would prefer to just do the first method, however at this point I'll take anything that works!
Upvotes: 1
Views: 1027
Reputation: 501
So I figured this out if any body else is having this issue. I can now test both mocked data created right in my jasmine test, as well as test the real data being sent in by my service.
Here is the code walkthrough:
Here is my angular controller I am testing:
// 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;
}
}
})();
Here is jasmine code that is properly unit testing the controller, to verify that the productResource.query()
method in my controller is assigning correct data to the `vm.products' variable. I will mock this data in jasmine.
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
is basically waiting for something - my controller in this case - to call the api and is then responding with the object [{'productId': 13}]
. This properly mocks my backend to allow us to do a unit test on the controller functionality.
the productResource code that actually makes the api call is behind the scenes in a factory I created, and am using in my controller. For the curious minds out there here is the productResource code:
(function () {
"use strict";
angular
.module("common.services")
.factory("productResource", ["$resource", productResource]);
function productResource($resource) {
return $resource('/api/products/:productId');
}
})();
Now, to answer my original question: Without mocking the http response in my jasmine test, if I wanted to test and see what our backend is sending to our controller, how do I do that?
Well, full disclaimer, I am not using a "real" backend. I am actually using $httpBackend in my actual angular app to simulate a real backend. This is because I followed an angular tutorial and that is just what the tutorial did. So, I need to add one line of code in that file. Here is a snippit of that file and the added line of code.
(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);
And now the jasmine test. It is the same as above with just a different it
method. So here is just the it
method:
it ('products should really be defined', function() {
$httpBackend.flush();
expect(ProductListController.products[0].productId).toBe(1);
});
I first flush the backend, since my controller already pinged the rest api. Then I check the data and sure enough, it contains what my mocked backend sent in. Done!
I imagine if you wanted to test your real backend for data correctness, the jasmine test should work fine for you.
sorry for the long winded answer, but I wanted to be thorough in case there are any noobs out there (like me) who have this same issue.
Upvotes: 1
Reputation: 11
$httpBackend is a ngMock module service that you can use to actually mock http response in your test code (it is kind of imitatation of real backend, so you can check how your application reacts to potential http call). On the other side, in production code, you are using $resource service. In this place it has nothing to do with $httpBackend.
Check out this code
it ('products should be defined', function() {
$httpBackend.expectGET('/api/products').respond({});
$httpBackend.flush();
expect(ProductListController.products).toBeDefined();
});
We added two lines of code to your test to imitate real backend, so that your productResource will receive data as if it was served from backend.
Upvotes: 1