How can I define a global $httpBackend for servicesSpec and controllersSpec in AngularJS? - testing

My controller relies on a service which does http requests.
Both controller and service tests require the httpBackend with the same data. Do you have any idea of a workaround?

I imagine your code looks like this:
angular.module('MyApp', [])
.controller('MyCtrl', function($scope, MyService) {
$scope.doSomething = function() {
MyService.doSomething();
};
})
.service('MyService', function($http) {
$http.get('some-url')
.success(function(response) {
// Handles the response
});
});
In order to unit test MyCtrl you need to mock out its dependencies: $scope and MyService. So in your test you can do this:
describe('MyCtrl', function() {
var MyServiceMock,
scope;
beforeEach(function() {
module('MyApp');
// Creates a mock (spy) for MyService
MyServiceMock = jasmine.createSpyObj('MyService', ['doSomething']);
inject(function($provide) {
// Tells Angular to use MyServiceMock instead of MyService
$provide.value('MyService', MyServiceMock);
});
inject(function($controller, $rootScope) {
scope = $rootScope.$new();
$controller('MyCtrl', { $scope: scope });
});
});
it('calls doSomething of MyService', function() {
$scope.doSomething();
expect(MyServiceMock.doSomething).toHaveBeenCalled();
});
});
That way you can test MyCtrl without having to use an actual instance of MyService.
I assume you know how to write tests for MyService using the $httpBackend service, so I won't post the code for it here.

Related

How to update same fixture file within the same BeforEeach hook and get the updated details as the response in Cypress [duplicate]

I am trying to write a test with the new cypress 6 interceptor method (Cypress API Intercept). For the test I am writing I need to change the reponse of one endpoint after some action was performed.
Expectation:
I am calling cy.intercept again with another fixture and expect it to change all upcomming calls to reponse with this new fixture.
Actual Behaviour:
Cypress still response with the first fixture set for the call.
Test Data:
In a test project I have recreated the problem:
test.spec.js
describe('testing cypress', () => {
it("multiple responses", () => {
cy.intercept('http://localhost:4200/testcall', { fixture: 'example.json' });
// when visiting the page it makes one request to http://localhost:4200/testcall
cy.visit('http://localhost:4200');
cy.get('.output').should('contain.text', '111');
// now before the button is clicked and the call is made again
// cypress should change the response to the other fixture
cy.intercept('http://localhost:4200/testcall', { fixture: 'example2.json' });
cy.get('.button').click();
cy.get('.output').should('contain.text', '222');
});
});
example.json
{
"text": "111"
}
example2.json
{
"text": "222"
}
app.component.ts
import { HttpClient } from '#angular/common/http';
import { AfterViewInit, Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
public text: string;
public constructor(private httpClient: HttpClient) { }
public ngAfterViewInit(): void {
this.loadData();
}
public loadData(): void {
const loadDataSubscription = this.httpClient.get<any>('http://localhost:4200/testcall').subscribe(response => {
this.text = response.body;
loadDataSubscription.unsubscribe();
});
}
}
app.component.html
<button class="button" (click)="loadData()">click</button>
<p class="output" [innerHTML]="text"></p>
Slightly clumsy, but you can use one cy.intercept() with a Function routeHandler, and count the calls.
Something like,
let interceptCount = 0;
cy.intercept('http://localhost:4200/testcall', (req) => {
req.reply(res => {
if (interceptCount === 0 ) {
interceptCount += 1;
res.send({ fixture: 'example.json' })
} else {
res.send({ fixture: 'example2.json' })
}
});
});
Otherwise, everything looks good in your code so I guess over-riding an intercept is not a feature at this time.
As of Cypress v7.0.0 released 04/05/2021, cy.intercept() allows over-riding.
We introduced several breaking changes to cy.intercept().
Request handlers supplied to cy.intercept() are now matched starting with the most recently defined request interceptor. This allows users to override request handlers by calling cy.intercept() again.
So your example code above now works
cy.intercept('http://localhost:4200/testcall', { fixture: 'example.json' });
// when visiting the page it makes one request to http://localhost:4200/testcall
cy.visit('http://localhost:4200');
cy.get('.output').should('contain.text', '111');
// now cypress should change the response to the other fixture
cy.intercept('http://localhost:4200/testcall', { fixture: 'example2.json' });
cy.get('.button').click();
cy.get('.output').should('contain.text', '222');
Cypress command cy.intercept has the
times parameter that you can use to create intercepts that only are used N times. In your case it would be
cy.intercept('http://localhost:4200/testcall', {
fixture: 'example.json',
times: 1
});
...
cy.intercept('http://localhost:4200/testcall', {
fixture: 'example2.json',
times: 1
});
See the cy.intercept example in the Cypress recipes repo https://github.com/cypress-io/cypress-example-recipes#network-stubbing-and-spying
const requestsCache = {};
export function reIntercept(type: 'GET' | 'POST' | 'PUT' | 'DELETE', url, options: StaticResponse) {
requestsCache[type + url] = options;
cy.intercept(type, url, req => req.reply(res => {
console.log(url, ' => ', requestsCache[type + url].fixture);
return res.send(requestsCache[type + url]);
}));
}
Make sure to clean requestsCache when needed.

ReferenceError: data is not defined in angular 6

Im calling a service from one of my component, via the assignGirdle function. While this service is being executed, I get the above error, but when I check in the network tab and click on the API call, in response in can see the data.
Note girdleNew is of type any. Also this function I'm calling on ngOnInit()
assignGirdle() {
this.diamondSearchService.getDistinctValues()
.subscribe((data) => {
this.girdleNew = data;
}, error => {
this.alertify.error(error);
});
}
The service:
getDistinctValues() {
return this.authHttp
.get(this.baseUrl + 'distinct/girdle')
.map(response => response.json())
.catch(this.handleError);
}
Check if you have imported service in your component. Below is my data service import.
import { DataService } from '../data.service';
Create object of the same in your component:
constructor(public data: DataService) { }
In my component I have called this getUser() method which is defined in DataService.
this.data.getUser(email).subscribe( data => {
if(data.length > 0){
console.log(data);
}
});
Below is my service:
getUser(email){
// definition
}
This works for me.

How do you unit test that methods such as ok() were called in aurelia-dialog using jasmine?

How do I go about unit testing a method that calls the DialogController? I want to test that this.controller.ok() was called.
ReprocessLotDialog.js
#inject(DialogController)
export class ReprocessLotDialog {
constructor(dialogController) {
this.controller = dialogController;
}
commitReprocessingLot() {
this.controller.ok({
reprocessLot: this.model
});
}
commitIncompleteBags(){
... do stuff ....
this.commitReprocessingLot();
}
}
myDialog.spec.js I've tried this but can't get it to work
describe("The ReprocessLotDialog module", ()=> {
let sut;
beforeEach(()=> {
var container = new Container().makeGlobal();
container.registerInstance(DialogController,controller => new DialogController());
sut = container.get(ReprocessLotDialog);
});
it("commitReprocessingLot() should call controller.ok", (done)=> {
spyOn(sut, "controller.ok");
sut.commitIncompleteBags();
expect(sut.controller.ok).toHaveBeenCalled();
done();
});
});
The test fails with TypeError: 'undefined' is not a function (evaluating 'this.controller.ok({ reprocessLot: this.model })')
As far as I understand it I'm passing the DialogController through DI into the container and container.get(ReprocessLotDialog) injects the DialogController into the ctor.
I've also tried
container.registerInstance(DialogController,{"dialogController":DialogController}); but that doesn't work either.
Many thanks
Your unit tests shouldn't be utilizing the DI container. You simply new up an instance of ReprocessLotDialog and pass in a mock of DialogController that you created. If you're using Jasmine, it would look something like this:
describe("The ReprocessLotDialog module", ()=> {
it("commitReprocessingLot() should call controller.ok", ()=> {
let dialogController = {
ok: (arg) = { }
};
spyOn(dialogController, "ok");
let sut = new ReprocessLotDialog(dialogController);
sut.commitIncompleteBags();
expect(dialogController.ok).toHaveBeenCalled();
});
});
You might also want to consider if testing if you shouldn't just be testing if commitReprocessingLot has been called instead of checking that it calls the method that's the only thing it does (at least in your example).
describe("The ReprocessLotDialog module", ()=> {
it("commitReprocessingLot() should call controller.ok", ()=> {
let dialogController = {
ok: (arg) = { }
};
let sut = new ReprocessLotDialog(dialogController);
spyOn(sut, "commitReprocessingLot");
sut.commitIncompleteBags();
expect(su.commitReprocessingLot).toHaveBeenCalled();
});
});
You can definitely stub out the controller or you can just spy on the instance method like this -
describe("The ReprocessLotDialog module", ()=> {
let container;
let sut;
let controller;
beforeEach(()=> {
container = new Container().makeGlobal();
controller = container.get(DialogController);
sut = container.get(ReprocessLotDialog);
});
it("commitReprocessingLot() should call controller.ok", (done)=> {
spyOn(controller, 'ok');
sut.commitIncompleteBags();
expect(controller.ok).toHaveBeenCalled();
done();
});
});
Basically you should be able to create an instance in your container of the controller that will get passed in and you can spyOn the method directly on the instance.

Using a json file to store configurations for a dojo application

I am writing a fontend web app using dojo that does a lot of calls to rest endpoints using xhr. I would like to have a place to store configurations for things like endpoint locations and html tag references. I thought I would use an xhr call to a json file to do this, but I am having trouble getting my functions to trigger in the right order/at all. Below is my main js file which has an init() function that I am passing as the callback to my conf initializer ("ebs/conf") module, also below. I have used the Chrome debugger to set breakpoints within my conf.get() method, and it looks as though it never gets called.
Can someone give me some advice please?
Main JS File:
// module requirements
require([ "dojo/dom", "dojo/on", "ebs/prices", "ebs/cart", "ebs/conf",
"dojo/ready" ], function(dom, on, prices, cart, conf, ready) {
ready(function() {
conf.get("/js/config.json", init());
function init(config) {
on(dom.byId("height"), "keyup", function(event) {
prices.calculate(config);
});
on(dom.byId("width"), "keyup", function(event) {
prices.calculate(config);
});
on(dom.byId("qty"), "keyup", function(event) {
prices.calculate(config);
});
on(dom.byId("grills"), "change", function(event) {
prices.calculate(config);
});
cart.putSampleCart();
cart.load(config);
}
});
});
And here is my 'conf' module ("ebs/conf"):
define(["dojo/json", "dojo/request/xhr"], function(json, xhr) {
return {
get : function(file, callback) {
// Create config object from json config file
var config = null;
xhr(file, {
handleAs : "json"
}).then(function(config) {
callback(config);
}, function(error) {
console.error(error);
return error;
});
}
}
});
Your are not passing the function as the callback. You are executing it and passing the result as the second argument.
conf.get("/js/config.json", init());
should be
conf.get("/js/config.json", init);

How to broadcast to other controllers when load with module.config or .run in Angularjs

I have a checking when reading the web page,then using the result to refresh sidebar by ng-repeat,but I have errors :
Uncaught Error: Unknown provider: $scope from myModule or
Uncaught Error: Unknown provider: $scope from sharedService
How can I resolve it?
Here is my code
module:
var myModule = angular.module('myModule', []);
service for broadcast:
myModule.factory('mySharedService', function($rootScope) { //service
var sharedService = {};
sharedService.keyHistory = [];
sharedService.linkHistory = [];
sharedService.prepForBroadcast = function(key,link) {
this.keyHistory = key;
this.linkHistory = link;
this.broadcastItem();
};
sharedService.prepForBroadcastAdd =function(key){
console.log(this.keyHistory.push(key));
//this.linkHistory = linkHistory+link;
this.broadcastItem();
};
sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};
return sharedService;
});
config to do Checking:
myModule.config(function($scope,sharedService){
$.ajax({
url:"/fly/AJAX",
type:"POST",
contentType:'application/x-www-form-urlencoded; charset=UTF-8',
datatype:"json",
success:function(data){
if(data!=null){
var loginResult = $.parseJSON(data);
if (loginResult.success == true){
console.log("login success");
$("#userLable").html(loginResult.userName+'('+loginResult.loginID+')');//
if (loginResult.hasHistory==true) {
sharedService.prepForBroadcast(loginResult.searchHistory,[]);
console.log("broadcast");
}
};
}
}
});
});
SideCtrl:
function SideCtrl($scope,sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.keyHistory =sharedService.keyHistory;
$scope.linkHistory = sharedService.linkHistory;
});
}
SideCtrl.$inject = ['$scope', 'mySharedService'];
THX !
The error is due to trying to request a $scope in a config block, which you can't do. If I understand what you're trying to do, then I also think you're over-complicating it. I'd solve the problem a little differently. The details would depend on your requirements and use case, but based on the information you gave...
I'd have a service responsible for communication with the server and storing the state:
app.factory( 'loginService', function ( $http ) {
var result;
function doRequest( data ) {
// just flesh out this post request to suit your needs...
return $http.post( '/fly/ajax', data, {} )
.then( function ( response ) {
// assuming you don't care about the headers, etc.
return response.data;
});
}
// Do it once initially
if ( ! angular.isDefined( result ) ) {
result = doRequest();
}
// return the service's public API
return {
getStatus: function () { return result; },
login: doRequest
};
});
Now the first time this service is requested, the $http request will be made. If you're accessing this from multiple controllers, the post will only occur once because of the isDefined statement. You can then use this in your controllers:
app.controller( 'MainCtrl', function( $scope, loginService ) {
loginService.getStatus().then( function ( data ) {
// do whatever you need to with your data.
// it is only guaranteed to exist as of now, because $http returns a promise
});
});
Every controller accesses it the same way, but it was still only called once! You can set values against the scope and access it from your views, if you want:
app.controller( 'MainCtrl', function( $scope, loginService ) {
loginService.getStatus().then( function ( data ) {
$scope.loginId = data.loginID;
});
});
And in your view:
<h1>Welcome, {{loginId || 'guest'}}!</h1>
And if you need to, you call the function again:
app.controller( 'MainCtrl', function( $scope, loginService ) {
// ...
loginService.login( $scope.user ).then( function ( data ) {
$scope.loginId = data.loginID;
});
// ...
});
As you can see, broadcasting an event is totally unnecessary.
I would do it differently. I would create some sort of more top-level controller, like function MainController($rootScope, $scope, sharedService) and wire it up with body: <body ng-controller='mainController' ng-init='init()'. After that you should create init() method in MainController.
Inside this initialization method I would call sharedService which should make AJAX request (via $http! that's the best practice, and it's very similar to jQuery) and broadcast proper event when required.
That way you make sure to call initialization just once (when MainController is initializing), you stick to the angular's best practices and avoid dodgy looking code.