This is my test:
describe('Controller: driversController', function () {
// First, we load the app's module
beforeEach(module('F1FeederApp'));
// Then we create some variables we're going to use
var driversController, scope;
beforeEach(inject(function ($controller, $rootScope, $httpBackend) {
// Here, we create a mock scope variable, to replace the actual $scope variable the controller would take as parameter
scope = $rootScope.$new();
// Then we create an $httpBackend instance. I'll talk about it below.
httpMock = $httpBackend;
// Here, we set the httpBackend standard reponse to the URL the controller is supposed to retrieve from the API
httpMock.expectJSONP("http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK").respond(
{"MRData": {"StandingsTable": {"StandingsLists" : [{"DriverStandings":[
{
"Driver": {
"givenName": 'Sebastian',
"familyName": 'Vettel'
},
"points": "397",
"nationality": "German",
"Constructors": [
{"name": "Red Bull"}
]
},
{
"Driver": {
"givenName": 'Fernando',
"familyName": 'Alonso'
},
"points": "242",
"nationality": "Spanish",
"Constructors": [
{"name": "Ferrari"}
]
},
{
"Driver": {
"givenName": 'Mark',
"familyName": 'Webber'
},
"points": "199",
"nationality": "Australian",
"Constructors": [
{"name": "Red Bull"}
]
}
]}]}}}
);
// Here, we actually initialize our controller, passing our new mock scope as parameter
driversController = $controller('driversController', {
$scope: scope
});
// Then we flush the httpBackend to resolve the fake http call
httpMock.flush();
}));
// Now, for the actual test, let's check if the driversList is actually retrieving the mock driver array
it('should return a list with three drivers', function () {
expect(scope.driversList.length).toBe(3);
});
// Let's also make a second test checking if the drivers attributes match against the expected values
it('should retrieve the family names of the drivers', function () {
expect(scope.driversList[0].Driver.familyName).toBe("Vettel");
expect(scope.driversList[1].Driver.familyName).toBe("Alonso");
expect(scope.driversList[2].Driver.familyName).toBe("Webber");
});
});
Questions:
In my first test, I write:
expect(scope.driversList.length).toBe(3);
This scope variable doesn't get set until this method in my controller fires:
angular.module('F1FeederApp.controllers', []).
/* Drivers controller */
controller('driversController', function($scope, ergastAPIservice) {
$scope.nameFilter = null;
$scope.driversList = [];
$scope.searchFilter = function (driver) {
var re = new RegExp($scope.nameFilter, 'i');
return !$scope.nameFilter || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName);
};
ergastAPIservice.getDrivers().success(function (response) {
//Digging into the response to get the relevant data
$scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings;
});
}).
So is my controller really a function that gets called automatically in my test? How? When does it get called?
Why are we using $rootScope here in the test?
For every test suite controller gets initialize first along with the beforeEach settings (if any).
Coming to your questions
1. Since controller is initialize and your below method is not wrapped inside any function it is been called and sets your driverList array
ergastAPIservice.getDrivers().success(function (response) {
//Digging into the response to get the relevant data
$scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings;
});
To avoid simply wrap it inside a function. To call this function on controller load you can use
ng-init
You can check this
Related
I am building a web application using React JS. I am writing integration tests using Cypress for my application. In my tests, I am reading JSON data from fixture files. When I read fixture file, it is throwing the following error.
TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Window'
--- property 'window' closes the circle
This is my code.
describe('Login', () => {
it('renders login form render the form controls', () => {
it('redirects to dashboard page when login is successful', () => {
// TODO: use fixture
// TODO: give alias
// TODO: mock me endpoint
const accessToken = 'fake access token'
const loginResponseJson = cy.fixture('superadmin-login.json');
})
})
This is my superadmin-login.json fixture file.
{
"accessToken": "fake access token",
"token": {
"id": "c09dd33fbdcbf780ddb4d784002f1b4c8413cc298f2465f5f3e3c3481b2aa7f586d14f04af163a1c",
"user_id": 31,
"client_id": 1,
"name": "User Access Token",
"scopes": [],
"revoked": false,
"created_at": "2022-05-24 20:39:48",
"updated_at": "2022-05-24 20:39:48",
"expires_at": "2025-05-24T20:39:48.000000Z"
}
}
What is wrong with my code and how can I fix it?
Reading a fixture file is an asynchronous operation, so this should work for you
cy.fixture('superadmin-login.json').then(loginResponseJson => {
const token = loginResponseJson.token;
...
})
Note, Cypress has it's own type of a-synchronicity (command-queue model), so you can't do this either
// won't work
const loginResponseJson = await cy.fixture('superadmin-login.json')
You could require it at the top of the spec if you like to minimise .then() callbacks.
const loginResponseJson = require('./cypress/fixtures/superadmin-login.json')
Loading the fixture first and then intercept request and return mock response works
cy.fixture('car/cars.json').then(cars => {
cy.intercept('GET', '/api/cars?limit=100', cars)
})
I am trying to make a basic GET call to an API using JSON API specifications with deeply nested relationship data. I'm experiencing an issue with axios or vue manipulating my data...
Paying attention to pickup and delivery relationships, the raw data looks like the following:
{
"data": {
"type": "parent",
"id": "34",
"attributes": {
// ...
},
"relationships": {
"r1": {
"data": [
{
"type": "nextparent",
"id": "62",
"attributes": {
// ..
},
"relationships": {
"pickup": {
"data": [
{
"type": "package",
"id": 521,
"attributes": {
// ...
}
},
// ...
]
},
"delivery": {
"data": []
}
}
},
// ...
]
}
}
}
}
... where pickup has an array of objects and delivery has an empty array. However, when I try to retrieve the response in the callback, delivery is an exact copy of pickup.
var promise = window.axios({
url: window.baseApi + '/my-object/34',
method: 'get'
})
promise.then(function (response) {
console.log(response)
})
Whenever you run console.log(response) in the callback, Vue's observers are applied to the response object, which makes me wonder if this is a Vue issue considering that only the relationships of the first r1 object experience this.
Screenshot of log from callback (incorrect):
Additionally, I checked the response from the axios transformResponse function and the raw json was correct (without observers). This is only happening in the callback.
Screenshot of log from transformResponse (correct):
Will update with fiddle, as soon as I can.
EDIT:
The axios function is being called in a vuex action. Here is the code:
fetchData ({commit, dispatch}) {
var promise = window.axios({
url: window.baseApi + '/my-object/34',
method: 'get'
})
promise
.then(function (response) {
console.log(response)
commit('toggleProgress', {key: 'fetchingData'})
commit('setActive', response.data.data)
dispatch('otherModule/setActiveShipments', response.data.data, {root: true})
dispatch('history/clear')
})
.catch(function () {
//
})
return promise
},
I have the scenario as follow:
I have a text box and button and whenever I add sth in textbox I want to add the text in the table my code is as follow:
var app = angular.module('app', []);
app.factory('Service', function() {
var typesHash = [ {
id :1,
name : 'lemon',
price : 100,
unit : 2.5
}, {
id : 2,
name : 'meat',
price : 200,
unit : 3.3
} ];
var localId = 3;
var service = {
addTable : addTable,
getData : getData,
};
return service;
function addTable(name) {
typesHash.push({id:localId++, name:name, price:100,unit:1});
}
function getData() {
return typesHash;
}
});
app.controller('table', function(Service) {
//get the return data from getData funtion in factory
this.typesHash = Service.getData();
//get the addtable function from factory
this.addTable = Service.addTable;
});
and the plnkr is as follow:
plnkr
Now as you can see I add whatever inside the text in the table and everything works fine but now I want to add whatever inside the textbox and also I want to get some information from the servlet and add those to the table as well. so for that I use ajax call as follow:
function addTable(name) {
typesHash.push({id:localId++, name:name, price:100,unit:1});
var responsePromise = $http.get("http://localhost:8080/purchase/AddInfo");
responsePromise.success(function(data, status, headers, config) {
typesHash.push( {id:data.id,name : data.name, price : data.price,unit:2.5 });
});
}
but when I use that I get he following error:
ReferenceError: $http is not defined
can anyone help? (just a quick note: this code is smaller version of my real code and I purposely used factory since I need it)
inside of your controller attr your should insert an $http argument:
app.controller('CTRL1', function($scope, $http){
//Now your can use $http methods
})
or insert $http argument in your service decleration if you are using $http request methods from inside of your service
I am working on new version of an app using legacy API (I have no controll of what the API returns etc.. ).
On the app init I request & store some site-wide info the factory which I have called stateFactory. Inside the stateFactory there is categories property (array of objects) which is storing the id -> category name relation.
Inside my app template I am using a filter to extract the name of the category by id {{ cat_id | categoryNameByIdFilter }} which is doing a lookup in the stateFactory.categories and returns the category name.
How do I write a unit test for such functionality (jasmine, mocha, chai, anything)?
// represantion of what the stateFactory looks like with some data in it
app.factory('stateFactory', ['', function(){
return {
categories = [
{ cat_id: 1, cat_name: "some category name" },
{ cat_id: 2, cat_name: "another category name" }
];
};
}])
// categoryNameByIdFilter
app.factory('categoryNameByIdFilter', ['stateFactory', function(stateFactiry){
return function(cat_id){
if ( !cat_id ) return null;
var cat_obj = _.findWhere(stateFactiry.categories, {
id: cat_id
});
if ( !cat_obj ) return null;
return cat_obj.cat_name;
};
}]);
I suggest using Jasmine and angular's mock module. You can create a mock of the stateFactory so that it does not hit a web service while unit testing. I have used Sinon to create my mocks and spies. You can, then, have angular inject your mock instead of the real service. This way, the only system under test is the categoryNameByIdFilter and not your web service.
// representation of what the stateFactory looks like with some data in it
app.factory('stateFactory', ['', function ()
{
return function ()
{
//This is the real stateFactory, which we are going to mock out.
};
}]);
// categoryNameByIdFilter - The system under test in this example
app.factory('categoryNameByIdFilter', ['stateFactory', '_', function (stateFactiry, _)
{
return function (cat_id)
{
if (!cat_id) return null;
var cat_obj = _.findWhere(stateFactiry.categories, {
id: cat_id
});
if (!cat_obj) return null;
return cat_obj.cat_name;
};
}]);
Given the code above, we can test categoryNameByIdFilter by doing this...
describe("categoryNameByIdFilter", function ()
{
beforeEach(module('YOUR_APP_MODULE'));
beforeEach(function ()
{
//The following line creates a mock of what we expect the state factory to return.
//We're mocking this because it is no the system under test, the filter is.
//A sinon 'stub' is a spy
mockStateFactory = sinon.stub({
categories: [
{ id: 1, cat_name: "some category name" },
{ id: 2, cat_name: "another category name" }
]
});
module(function ($provide)
{
//When Angular asks for a stateFactory, give them this mock instead
$provide.value('stateFactory', mockStateFactory);
});
});
//You can inject a filter using the "inject" method below
it("should filter by id", inject(function (categoryNameByIdFilter)
{
//Wrap categoryNameByIdFilter in a spy so that we can make assertions off of it.
var spy = sinon.spy(categoryNameByIdFilter);
var result = spy(1);
expect(result).toEqual("some category name");
expect(spy.calledBefore(mockStateFactory)).toBeTruthy();
expect(spy.returned("some category name")).toBeTruthy();
sinon.assert.calledOnce(spy);
spy(2);//Returns something besides "some category name"
expect(spy.alwaysReturned("some category name")).not.toBeTruthy();
sinon.assert.calledTwice(spy);
}));
});
I cant seem to find a way to make django-dajaxice have its callback inside same scoped object from which made the initial call.
MyViewport = Ext.extend(MyViewportUi, {
initComponent: function() {
MyViewport.superclass.initComponent.call(this);
},
LoadRecordsCallback: function(data){
if(data!='DAJAXICE_EXCEPTION')
{ alert(data); }
else
{ alert('DAJAXICE_EXCEPTION'); }
},
LoadRecords: function(){
Dajaxice.Console.GetUserRecords(this.LoadRecordsCallback);
}
});
var blah = new MyViewport();
blah.LoadRecords();
I'm on django, and like the calling syntax to django-dajaxice. I'm using Extjs 3.2 and tried passing a Ext.createCallback but Dajax's returning eval seems to only want a string for the callback.
BozoJoe, this should work.
MyViewport = Ext.extend(MyViewportUi, {
initComponent: function() {
MyViewport.superclass.initComponent.call(this);
},
LoadRecordsCallback: function(data){
if(data!='DAJAXICE_EXCEPTION')
{ alert(data); }
else
{ alert('DAJAXICE_EXCEPTION'); }
},
LoadRecords: function(){
Dajaxice.Console.GetUserRecords('blah.LoadRecordsCallback');
}
});
var blah = new MyViewport();
blah.LoadRecords();
I'm not familiar with django at all, but I think I understand the problem.
It seems that the API mandates that you pass a string which will be eval'd as a function call, so you must pass the name of the function, rather than the function itself.
This in turn means that it must be a name that is meaningful at the window scope - either a function defined outside of an Ext class (e.g. "myGlobalFunction"), or a member function of an Ext class that is accessible as a variable (e.g. "window.blah.LoadRecordsCallback")