Angular Unit Test: How to mock properties in a method? - javascript

Here is the service that I am trying to test:
#Injectable()
export class BomRevisiosnsService {
constructor(
private baseService: BaseService,
private appConstants: AppConstants,
private dmConstants: DMConstants
) { }
public getRevisionsData(): any {
var itemId = this.appConstants.userPreferences.modelData['basicDetails']['itemId'];
let url = this.dmConstants.URLs.GETBOMREVISIONS + itemId + "/GetRevisionsAsync";
let headers = {
"Content-Type": "application/json",
UserExecutionContext: JSON.stringify(this.appConstants.userPreferences.UserBasicDetails),
}
if (itemId != null || itemId != undefined) {
return this.baseService.getData(url, headers).map(response => {
return response;
});
}
}
}
spec file
describe('bom-revisions.service ',()=>{
let bomRevisiosnsService:BomRevisiosnsService;
let baseService: BaseService;
let appConstants: AppConstants;
let dmConstants: DMConstants;
beforeEach(()=>{
baseService=new BaseService(null,null);
appConstants=null;
dmConstants=null;
bomRevisiosnsService=new BomRevisiosnsService(baseService,appConstants,dmConstants);
});
it('getRevisionsData() call base service getData()',()=>{
let spy = spyOn(baseService, 'getData').and.returnValue(Observable.of())
bomRevisiosnsService.getRevisionsData();
expect(baseService.getData).toHaveBeenCalled();
});
})
Error: TypeError: Cannot read property 'userPreferences' of null
I believe I need to provide some mock value for this.appConstants.userPreferences.modelData['basicDetails']['itemId'];
and this.dmConstants.URLs.GETBOMREVISIONS + itemId + "/GetRevisionsAsync";

Yes, indeed, you need to provide a valid value for appConstants and dmConstants because the call to bomRevisiosnsService.getRevisionsData() uses that information internally.
So, instead of assigning null to appConstants and dmConstants, you could create an objects with some valid data, like this:
appConstants = {
userPreferences: {
modelData: {
basicDetails: {
itemId: 3 // some other valid value here is fine
}
},
UserBasicDetails: {
// some valid values here, maybe
}
}
};
dmConstants = {
URLs: {
GETBOMREVISIONS: 'revisions' // or just some valid value according to the use case
}
};
And the same goes to baseService.
In general, you need to create valid stub, mock, etc for all object, services, etc that are used internally by the service you're testing.

Related

How to make a single new HttpParams() request validating if there is data return

I have a function that when the component is loaded, it returns specific data of a certain parameter (ex: /app/items) and displays them in an information balloon, but if I access a child route (ex: /app/items/ create) or (ex: /app/items/1/view) I need to do a validation to see if there is informational data in this route to display, but if not, I need to display the information from the previous parameter (ex: /app/items) . I managed to create this function below that fulfills this, but with a request inside another, which I believe is not the most appropriate. So I'm trying to refactor so that only one request is made validating these parameters. It should also be considered that if the route has numbers, it needs to be filtered and returned an array with just the strings before doing the join('/'), as you can see there in the constructor. I'm new to the area, so I'm trying to find some background to refactor this function.
public information: InformationList | any
public isLoading:boolean = true
error: any;
showError: boolean = false
public currentPageUrl = null;
public output = null
other: any;
constructor(
public screenInfoService: ScreenInfoService,
public loaderService: LoaderService,
public router: Router
) {
this.currentPageUrl = this.router.url.split("/").filter((item: any) => {
return isNaN(item)
})
}
load() {
this.output = this.currentPageUrl.join("/");
let params = new HttpParams();
params = params.set('orderBy', 'id');
params = params.set('search', `path:${this.output}`);
params = params.set('searchFields', `path:ilike`);
params = params.set('searchJoin', 'and');
this.screenInfoService.getAll(params).subscribe(
success => {
if (success.length === 0) {
let output2 = null
output2 = this.currentPageUrl.slice(0,2).join("/");
params = params.set('orderBy', 'id');
params = params.set('search', `path:${output2}`);
params = params.set('searchFields', `path:ilike`);
params = params.set('searchJoin', 'and');
this.screenInfoService.getAll(params).subscribe(
success => {
this.information = success
}, error => {
this.error = 'MSG.T224';
this.showError = true;
}
)
} else {
this.information = success
}
}, error => {
this.error = 'MSG.T224';
this.showError = true;
});
}
ngOnInit(): void {
this.load()
}

JS Design Pattern for setting a var set for functions

I am working on a react project but I think this question relates to all JS
I have a Token file that contains the following functions:
export default {
set(token) {
Cookies.set(key, token);
return localStorage.setItem(key, token);
},
clear() {
Cookies.remove(key);
return localStorage.removeItem(key);
},
get() {
try {
const retVal = localStorage.getItem(key) || '';
return retVal;
} catch (e) {
return '';
}
},
Now I want to add a set of what are essentially environment variables for the domain of these 3 functions. In my case its based on window.location.hostname but could really be anything.
In this instance lets say we want key to be dev, uat or prod based on window.location.hostname
getKey = host => {
if(host === 'a')
return 'dev'
elseIf (host === 'b')
return 'uat'
else
return 'prod'
}
I think the above is fairly standard to return the key you want. but what if your key has 6 vars, or 8, or 20. How could you set all the vars so that when you call set(), clear() and get() that they have access to them?
Basically I want to wrap the export in a function that sets some vars?
To illustrate this a bit more
class session extends Repo {
static state = {
current: false,
};
current(bool) {
this.state.current = bool;
return this;
}
query(values) {
<Sequelize Query>
});
}
export session = new Session();
using this I can call current(true).session() and sessions state would be set to true. I want to apply a similar pattern to the Token file but I don't want to change all my calls from Token.set(token) to Token.env(hostname).set(token)
This acomplished what I wanted, I had to call the function from within the others as window is not available on load. It essentially illustrates the pattern I was looking for. Thanks for Jim Jeffries for pointing me towards the answer.
class Token {
constructor(props) {
this.state = {
testKey: null,
};
}
setCred = host => {
if (host === 'uat') {
this.state.testKey = 'uat';
} else if (host === 'prod') {
this.state.testKey = 'prod';
} else {
this.state.testKey = 'dev';
}
};
set(token) {
this.setCred(window.location.hostname);
Cookies.set(testKey, token);
return localStorage.setItem(testKey, token);
}
clear() {
this.setCred(window.location.hostname);
Cookies.remove(testKey);
return localStorage.removeItem(testKey);
}
get() {
this.setCred(window.location.hostname);
try {
const retVal = localStorage.getItem(key) || '';
return retVal;
} catch (e) {
return '';
}
}
}
export default new Token();
If anyone else has another idea please share.

typescript: Helper class with nested method with return

I'm trying to create a JSON Helper class by calling method in top of another. i.e. second method take result from first method and process it. Also first method should give Class Object. If possible direct result.
Expected api and result from the Helper is as follow.
JSONMethods.parse("{name: 'hello'}") // { name: 'hello'}
JSONMethods.parse("{name: 'hello'}").format() // { name: 'hello'}
JSONMethods.parse("{name: 'hello'}").extract(Department); // { id: 0, name: 'hello'}
I've created a class as JSONMethods and defined those methods. but not sure how to progress. Find the code below.
class Department {
id: number = 0;
constructor() { }
}
class JSONMethods {
private static data;
static parse<U>(json: string | U): JSONMethods {
if (typeof json == 'string') {
this.data = JSON.parse(json);
}
else {
this.data = json;
}
return json; // not actually returing JSONMethods
}
static format() {
return this.data;
}
static extract<T>(entity: T) {
return Object.assign(new entity(), this.data); // getting compilation issue with new entity(). as *Cannot use 'new' with an expression whose type lacks a call or construct signature.*
}
}
I'm expecting the result as above. Also, extract method should not available top of parse method not in JSONMethods. I can make it as private. But how to access extract method from parse.
Bit confused. Can anyone support on this.
You need to pass in the constructor of the class not an instance of the class. So instead of entity: T it should be entity: new () => T
class JSONMethods {
private static data;
static parse<U>(json: string | U): typeof JSONMethods {
if (typeof json == 'string') {
this.data = JSON.parse(json);
}
else {
this.data = json;
}
return this;
}
static format() {
return this.data;
}
static extract<T>(entity: new () => T) {
return Object.assign(new entity(), this.data); // getting compilation issue with new entity(). as *Cannot use 'new' with an expression whose type lacks a call or construct signature.*
}
}
JSONMethods.parse("{name: 'hello'}") // { name: 'hello'}
JSONMethods.parse("{name: 'hello'}").format() // { name: 'hello'}
JSONMethods.parse("{name: 'hello'}").extract(Department);
Might I also suggest avoiding use of static fiedls in this case, you may run into troubles later if someone does not use the functions in the way you expect them to.
class JSONMethods {
private data;
static parse<U>(json: string | U): JSONMethods {
var r = new JSONMethods()
if (typeof json == 'string') {
r.data = JSON.parse(json);
}
else {
r.data = json;
}
return r;
}
format() {
return this.data;
}
extract<T>(entity: new () => T) {
return Object.assign(new entity(), this.data); // getting compilation issue with new entity(). as *Cannot use 'new' with an expression whose type lacks a call or construct signature.*
}
}
let s = JSON.stringify({name: 'hello'});
JSONMethods.parse(s) // { name: 'hello'}
JSONMethods.parse(s).format() // { name: 'hello'}
JSONMethods.parse(s).extract(Department);

Angular 2 / ES6 / Filter an object object based on a property

I am calling two API to return objects of data. Than run through for each of them and search if it has a value.
I want to check if one of these obj has a vas value matches.
getdata(slug){
this._apiService.getPages().subscribe(data =>{
this.pageobj = data
console.log('this page obj',this.pageobj)
})
this._apiService.getPosts().subscribe(data =>{
this.postsobj = data;
console.log('this post obj',this.postsobj)
})
}
this.pageobj is an object
this.postsobj
in both responses they had a property 'slug'.
I would like to check if in this.postsobj or this.pageobj has an object that contains 'slug' == 'hello-word', if so to return me object and store in var this.content
UPDATE
export class PageSingleComponent implements OnInit {
page: Page;
pageobj:any;
postsobj:any;
pageobjCheck:any
postsobjCheck:any
pageSlug:any;
content =new Array<any>();
constructor( private _apiService: apiService, private route: ActivatedRoute ) { }
getdata(slug){
this._apiService.getPages().subscribe(data =>{
this.pageobj = data
this.content.push(_.filter(this.pageobj, { 'slug':' hello-world' }));
})
this._apiService.getPosts().subscribe(data =>{
this.postsobj = data;
this.content.push(_.filter(this.postsobj, { 'slug':' hello-world' }));
})
}
ngOnInit() {
this.route.params.forEach((params: Params) => {
// Get slug from the rout
let slug = params['pageslug'];
console.log('slug is catcheds', slug)
this.pageSlug = params['pageslug'];
this.getdata(slug)
// Run functions
//
});
}
}
I think you want to use Filter function.
In your callback function passed to map function you want to check whether your response object form array has slug property which is equal to 'hello world'. Your code will like like this:
var content = response.filter(obj => obj && obj.slug === 'hello-world');
I prefer using lodash as below,
this.content =new Array<any>();
this.content.push(_.filter(this.pageobj, { 'slug':' hello-world' });
this.content.push(_.filter(this.postsobj, { 'slug':' hello-world' });
Alternatively you can handle it in the service using takeWhile operator
getPages(){
return this.http.get(...)
.takeWhile(data=>{
if(data.slug ==== 'hello-world'){
return data;
}
})
}

MobX not setting observable properly in my class

I'm trying to make my react app as dry as possible, for common things like consuming a rest api, I've created classes that act as stores with predefined actions to make it easy to modify it.
Behold, big code:
import {autorun, action, observable} from 'mobx'
export function getResourceMethods(name) {
let lname = name.toLowerCase()
let obj = {
methods: {
plural: (lname + 's'),
add: ('add' + name),
addPlural: ('add' + name + 's'),
rename: ('rename' + name),
},
refMethods: {
add: ('add' + name + 'ByRef'),
addPlural: ('add' + name + 'sByRef'),
rename: ('rename' + name + 'ByRef'),
setRef: ('set' + name + 'Ref'),
},
fetchMethods: {
pending: (lname + 'Pending'),
fulfilled: (lname + 'Fulfilled'),
rejected: (lname + 'Rejected'),
}
}
return obj
}
class ResourceItem {
#observable data;
#observable fetched = false;
#observable stats = 'pending';
#observable error = null;
constructor(data) {
this.data = data;
}
}
class ResourceList {
#observable items = [];
#observable fetched = false;
#observable status = 'pending';
constructor(name) {
this['add' + name + 's'] = action((items) => {
items.forEach((item, iterator) => {
this.items.push(item.id)
})
})
}
}
class ResourceStore {
constructor(name, resourceItem, middleware) {
let {methods} = getResourceMethods(name)
this.middleware = middleware || []
let items = methods.plural.toLowerCase()
this[items] = observable({}) // <--------------- THIS DOES NOT WORK!
// Add resource item
this[methods.add] = action((id, resource) => {
let item = this[items][id], data;
if (item && item.fetched) {
data = item.data
} else {
data = resource || {}
}
this[items][id] = new resourceItem(data)
this.runMiddleware(this[items][id])
})
// Add several resource items
this[methods.addPlural] = action((resources) => {
resources.forEach((resource, iterator) => {
this[methods.add](resource.id, resource)
})
})
// Rename resource item
this[methods.rename] = action((oldId, newId) => {
let item = this[items][oldId]
this[items][newId] = item
if (oldId !== newId) {
delete this[items][oldId]
}
})
// Constructor ends here
}
runMiddleware(item) {
let result = item;
this.middleware.map(fn => {
result = fn(item)
})
return result
}
}
class ReferencedResourceStore extends ResourceStore {
#observable references = {}
constructor(name, resource, middleware) {
super(name, resource, middleware)
let {methods, refMethods, fetchMethods} = getResourceMethods(name)
let getReference = (reference) => {
return this.references[reference] || reference
}
this[refMethods.setRef] = action((ref, id) => {
this.references[ref] = id
})
this[refMethods.add] = action((ref, data) => {
this[methods.add](getReference(ref), data)
this[refMethods.setRef](ref, getReference(ref))
})
this[refMethods.rename] = action((ref, id) => {
this[methods.rename](getReference(ref), id)
this[refMethods.setRef](ref, id)
})
// *** Fetch *** //
// Resource pending
this[fetchMethods.pending] = action((ref) => {
this[refMethods.add](ref)
})
// Resource fulfilled
this[fetchMethods.fulfilled] = action((ref, data) => {
this[refMethods.add](ref, data)
this[refMethods.rename](ref, data.id)
let item = this[methods.plural][data.id];
item.fetched = true
item.status = 'fulfilled'
})
}
}
export {ResourceItem, ResourceList, ResourceStore, ReferencedResourceStore}
Now I'm just creating a simple user store:
class UserResource extends ResourceItem {
constructor(data) {
super(data)
}
#observable posts = new ResourceList('Posts')
#observable comments = new ResourceList('Comment')
}
// Create store
class UserStore extends ReferencedResourceStore {}
let store = new UserStore('User', UserResource)
And mobx-react connects just fine to the store, can read it as well. BUT, whenever I do any changes to the items (users in this case, the name of the property is dynamic) property, there are no reactions. I also noticed that in chrome, the object property does not have a "invoke property getter" in the tree view:
Didn't read the entire gist, but if you want to declare a new observable property on an existing object, use extendObservable, observable creates just a boxed observable, so you have an observable value now, but not yet an observable property. In other words:
this[items] = observable({}) // <--------------- THIS DOES NOT WORK!
should be:
extendObservable(this, {
[items] : {}
})
N.b. if you can't use the above ES6 syntax, it desugars to:
const newProps = {}
newProps[items] = {}
extendObservable(this, newProps)
to grok this: https://mobxjs.github.io/mobx/best/react.html
Edit: oops misread, you already did that, it is not hacky but the correct solution, just make sure the extend is done before the property is ever used :)
I found a hacky solution:
First off, use extendObservable instead (this is the correct solution) and then use a fresh version of the object and set it as the property.
let items = methods.plural.toLowerCase()
extendObservable(this, {
[items]: {}
})
// Add resource item
this[methods.add] = action((id, resource) => {
let item = this[items][id], data;
if (item && item.fetched) {
data = item.data
} else {
data = resource || {}
}
this[items][id] = new resourceItem(data)
this.runMiddleware(this[items][id])
this[items] = {...this[items]}
})
This works, not sure if there's a better solution.
Your options are using extendObservable or using an observable map.
For reference see the documentation of observable and specifically:
To create dynamically keyed objects use the asMap modifier! Only initially existing properties on an object will be made observable, although new ones can be added using extendObservable.

Categories

Resources