Jasmine testing - Error upon spy definition - javascript

I have the following classes
export class Init {
constructor(url: URL) {}
getClient(url: URL): Client {
return new Client(url);
}
}
and the client is defined like
export class Client {
constructor(readonly url: URL) {}
foo(s: String): Promise<void> {}
// many other methods
}
and now I am trying to test this like so - with Jasmine
it('foo should be invoked', done => {
const init = new Init('test-url');
spyOn(init.getClient,'foo');
...
}
On the spy definition I get this error
Argument of type 'string' is not assignable to parameter of type 'never'
Why can I resolve this? Init's getClient method returns a Client object. Shouldn't the spy be able to identify this type?
My end result should look like this
it('foo should be invoked', done => {
const init = new Init('test-url');
spyOn(init.getClient,'foo');
expect(initCommand.getClient.foo).toHaveBeenCalledTimes(1);
}

You can only spyOn public methods. I would do this:
// mock `getClient` object however you like (some examples below)
// Return the object/value right away
spyOn(init, 'getClient').and.returnValue({ foo: (s: string) => Promise.resolve(s) });
// call a fake function every time init.getClient is called
spyOn(init, 'getClient').and.callFake((url) => return {});

Related

Memoize object lazily so that first attempt to access it would load it

Is there some npm package for memoizing object lazily, so that the first attempt to access it would load it?
The problem:
// service
class Service {
private readonly pathMap = {
user: process.env.USER_PATH,
post: process.env.POST_PATH,
page: process.env.PAGE_PATH,
}
getPath(entityType: EntityType) {
return this.pathMap[entityType];
}
}
export const service = new Service();
// service.spec.ts
import { service } from './service';
import { loadEnvVars } from '#app/loadEnvVars';
describe('service', () => {
beforeAll(loadEnvVars);
it('should return path', () => {
expect(service.getPath('user')).toBe(process.env.USER_PATH);
expect(service.getPath('post')).toBe(process.env.POST_PATH);
expect(service.getPath('page')).toBe(process.env.PAGE_PATH);
});
});
The tests will fail because the singleton service will load before the loadEnvVars due to the import of the service (before the beforeAll), which means the env vars will be set to undefined in the service pathMap.
The proposed solution:
I know there are several ways to fix it, but IMO the best solution would be to somehow lazy load the pathMap object so that the first attempt to get something from it will actually init the variable's value.
Here's a function I wrote to tackle this (Typescript):
export function lazy<T extends (...args: any[]) => any>(factory: T): ReturnType<T> {
let obj: ReturnType<T> | undefined;
const proxy = new Proxy(
{},
{
get(_, key) {
if (!obj) {
obj = factory();
}
return obj[key];
}
}
);
return proxy as ReturnType<T>;
}
Now the service will look like this instead:
class Service {
private readonly pathMap = lazy(() => ({
user: process.env.USER_PATH,
post: process.env.POST_PATH,
page: process.env.PAGE_PATH,
}));
getPath(entityType: EntityType) {
return this.pathMap[entityType];
}
}
export const service = new Service();
Now the tests will pass.
Note: In this solution lazy returns a read-only object. It can be changed of course.
The question:
Is there some NPM library out there that provides something like that already? Cause if not I think I might publish it myself.

Class composition in TypeScript

I want to build a class that can compose multiple objects and use any of their interfaces.
Class A can use any of the interfaces of Class B and C
B can use any of the interfaces of C
C can use any of the interfaces of B
I have the above functionality written in JavaScript and I was wondering what's the best and correct way to achieve the same using TypeScript:
import { findLast, isFunction } from "lodash";
class Composite {
constructor(behavior) {
this.behaviors = [];
if (behavior) {
this.add(behavior);
}
}
add(behavior) {
behavior.setClient(this);
this.behaviors.push(behavior);
return this;
}
getMethod(method) {
const b = findLast(this.behaviors, (behavior) =>
isFunction(behavior[method])
);
return b[method].bind(b);
}
}
class Behavior1 {
foo() {
console.log("B1: foo");
}
foo2() {
console.log("B1: foo2");
this.getMethod("bar")();
}
setClient(client) {
this.client = client;
}
getMethod(method) {
return this.client.getMethod(method);
}
}
class Behavior2 {
foo() {
console.log("B2: foo");
this.getMethod("foo2")();
}
bar() {
console.log("B2: bar");
}
setClient(client) {
this.client = client;
}
getMethod(method) {
return this.client.getMethod(method).bind(this);
}
}
const c = new Composite();
c.add(new Behavior1());
c.add(new Behavior2());
c.getMethod("foo")();
c.getMethod("bar")();
// Output:
// B2: foo
// B1: foo2
// B2: bar
// B2: bar
Link to codesandbox: https://codesandbox.io/s/zen-poitras-56f4e?file=/src/index.js
You can review my other answer to see some of the issues and concerns with the previous approach. Here I've created a completely different version from the ground up. There is less code repetition and less tight coupling between the classes.
Behaviors no longer call methods directly and no longer store a reference to the client. Instead, they receive the client (or any object which call get and call methods) as an argument of their register method.
We define any object which can lookup and call methods as a MethodAccessor
interface MethodAccessor {
getMethod(name: string): () => void;
safeCallMethod(name: string): boolean;
}
We define any object that provides behaviors through a register method as a BehaviorWrapper. These objects can call functions from other objects by calling getMethod or safeCallMethod on the helper argument.
type KeyedBehaviors = Record<string, () => void>;
interface BehaviorWrapper {
register(helper: MethodAccessor): KeyedBehaviors;
}
A behavior which does not need instance variables could be a pure function rather than a class.
const functionBehavior = {
register(composite: MethodAccessor) {
return {
foo: () => console.log("B1: foo"),
foo2: () => {
console.log("B1: foo2");
composite.safeCallMethod("bar");
}
};
}
};
Class behaviors can make use of instance variables in their methods.
class ClassBehavior {
name: string;
constructor(name: string) {
this.name = name;
}
bar = () => {
console.log(`Hello, my name is ${this.name}`);
};
register() {
return {
bar: this.bar
};
}
}
There is some redundancy here when defining a method like bar separately rather than inline as an arrow function within the return object. The reason that I am having the methods come from register rather than using all class methods is so that I can have stricter typing on them. You could have methods in your class which do require args and as long as they aren't part of the register returned object then it's not a problem.
Our class Composite now stores its behaviors in a keyed object rather than an array. Newly added behaviors of the same name will overwrite older ones. Our getMethod is typed such that it always returns a method, and will throw an Error if none was found. I've added a new method safeCallMethod to call a method by name. If a method was found, it calls it and returns true. If no method was found, it catches the error and returns false.
class Composite implements MethodAccessor {
behaviors: KeyedBehaviors = {};
constructor(behavior?: BehaviorWrapper) {
if (behavior) {
this.add(behavior);
}
}
// add all behaviors from a behavior class instance
add(behavior: BehaviorWrapper): this {
this.behaviors = {
...this.behaviors,
...behavior.register(this)
};
return this;
}
// lookup a method by name and return it
// throws error on not found
getMethod(method: string): () => void {
const b = this.behaviors[method];
if (!b) {
throw new Error(`behavior ${method} not found`);
}
return b;
}
// calls a method by name, if it exists
// returns true if called or false if not found
safeCallMethod(method: string): boolean {
try {
this.getMethod(method)();
return true;
} catch (e) {
return false;
}
}
}
There's a lot that's not ideal about your setup. I might post a separate answer with an alternate setup, but for now I just want to show you how to convert your code to typescript.
Keep in mind that typescript errors exist to help you prevent runtime errors, and there are some genuine potential runtime errors that we need to avoid. If a Behavior calls getMethod before calling setClient to set this.client that will be a fatal error. If you try to call the returned method from getMethod on a Composite or a Behavior where the name didn't match a method that's another fatal error. And so on.
You choose to handle certain situations by throwing an Error with the expectation that it will be caught later on. Here I am preferring to "fail gracefully" and just do nothing or return undefined if we can't do what we want. The optional chaining ?. helps.
When defining an interface for a function argument, it's best to keep it to the minimum necessities and not require any extraneous properties.
The only thing that a Behavior requires of its Client is a getMethod method.
interface CanGetMethod {
getMethod(name: string): MaybeMethod;
}
We use the union of undefined and a void function in a few places, so I am saving it to an alias name for convenience.
type MaybeMethod = (() => void) | undefined;
The Composite calls setClient on its behaviors, so they must implement this interface.
interface CanSetClient {
setClient(client: CanGetMethod): void;
}
It also expects that its methods take zero arguments, but we can't really declare this with the current setup. It is possible to add a string index to a class, but that would conflict with our getMethod and setClient arguments which do require arguments.
One of the typescript errors that you get a bunch is `Cannot invoke an object which is possibly 'undefined', so I created a helper method to wrap a function call.
const maybeCall = (method: MaybeMethod): void => {
if (method) {
method();
}
};
In typescript, classes need to declare the types for their properties. Composite gets an array of behaviors behaviors: CanSetClient[]; while the behaviors get a client client?: CanGetMethod;. Note that the client must be typed as optional because it is not present when calling new().
After that, it's mostly just a matter of annotating argument and return types.
I have declared the interfaces that each class implements, ie. class Behavior1 implements CanGetMethod, CanSetClient, but this is not required. Any object fits the interface CanGetMethod if it has a getMethod property with the right types, whether it explicitly declares CanGetMethod in its type or not.
class Composite implements CanGetMethod {
behaviors: CanSetClient[];
constructor(behavior?: CanSetClient) {
this.behaviors = [];
if (behavior) {
this.add(behavior);
}
}
add(behavior: CanSetClient): this {
behavior.setClient(this);
this.behaviors.push(behavior);
return this;
}
getMethod(method: string): MaybeMethod {
const b = findLast(this.behaviors, (behavior) =>
isFunction(behavior[method])
);
return b ? b[method].bind(b) : undefined;
}
}
class Behavior1 implements CanGetMethod, CanSetClient {
client?: CanGetMethod;
foo() {
console.log("B1: foo");
}
foo2() {
console.log("B1: foo2");
maybeCall(this.getMethod("bar"));
}
setClient(client: CanGetMethod): void {
this.client = client;
}
getMethod(method: string): MaybeMethod {
return this.client?.getMethod(method);
}
}
class Behavior2 implements CanGetMethod, CanSetClient {
client?: CanGetMethod;
foo() {
console.log("B2: foo");
maybeCall(this.getMethod("foo2"));
}
bar() {
console.log("B2: bar");
}
setClient(client: CanGetMethod) {
this.client = client;
}
getMethod(method: string): MaybeMethod {
return this.client?.getMethod(method)?.bind(this);
}
}
const c = new Composite();
c.add(new Behavior1());
c.add(new Behavior2());
maybeCall(c.getMethod("foo"));
maybeCall(c.getMethod("bar"));

Angular: pass a method as parameter

I have these two methods which are almost similar:
private firstFunction () {
this.serviceOne.methodOne().subscribe(
res => {
return resultOne = res;
},
err => {}
);
}
private secondFunction () {
this.serviceTwo.methodTwo().subscribe(
res => {
return resultTwo = res;
},
err => {}
);
}
I want to write a generic function, like this:
genericFunction (service ,method , result ) {
service.method().subscribe(
res => {
return result = res;
},
err => {}
);
}
And consequently I want to get something like this working:
genericFunction (serviceOne , methodOne , resultOne );
genericFunction (serviceTwo , methodTwo , resultTwo );
Actually, I cannot find how to pass methodOne and methodTwo as params. Any sugestions?
There are several issues in your code.
Firstly, you want to modify the field you pass in as a parameter (as suggested by result = res. You can't pass in a reference to a field, but you can pass in the field name, and use indexing to change the field. keyof T will allow you to pass in the field in a type safe way.
Secondly if you want to access a method on a service. Again we can do this passing in the method name, and we can constrain the service to have a method with the passed in method name, that returns an Observable. The result of the Observable can also be constrained to be of the same type of the field we are going to assign it to in order for the method to be fully type safe.
declare class Service1 {
method1() : Observable<number>
}
declare class Service2 {
method2() : Observable<string>
}
class MyClass {
resultOne!: number;
resultTwo!: string;
constructor() {
this.genericFunction(new Service1(), "method1", "resultOne");
this.genericFunction(new Service2(), "method2", "resultTwo");
this.genericFunction(new Service1(), "method1", "resultTwo"); // error resultTwo is a string, the method return Observable<number>
this.genericFunction(new Service2(), "method", "resultTwo"); // error method does not exit on Service2
this.genericFunction(new Service2(), "method2", "resultTwo2"); // error field does not exist on type
}
genericFunction<MethodKey extends string, ResultKey extends keyof MyClass>(service:Record<MethodKey, ()=> Observable<MyClass[ResultKey]>>, method:MethodKey, result: ResultKey){
service[method]().subscribe(
res => this[result] = res,
err => {}
);
}
}
Note We could have also passed in the function as a function not just as a name, but directly a typed function. The disadvantage of this is that we either have to use bind to ensure the service method will still have the correct this when it's called, or use an arrow function when calling (again to ensure the service method has the correct this). This is error prone though, bind results in an untyped function, so we can't check compatibility to the field, and someone might pass service.method directly and no error would be reported until runtime:
class MyClass {
resultOne!: number;
resultTwo!: string;
constructor() {
var service1 = new Service1()
var service2 = new Service2()
this.genericFunction(()=> service1.method1(), "resultOne");
this.genericFunction(()=> service2.method2(), "resultTwo");
this.genericFunction(service2.method2, "resultTwo"); // no error, depending on the implementation of method2 it might or might not work
this.genericFunction(service2.method2.bind(service2), "resultOne"); // no error, the service call will work, but we store it in an incompatible variable
this.genericFunction(()=> service1.method1(), "resultTwo");// error resultTwo is a string, the method return Observable<number>
this.genericFunction(()=> service2.method2(), "resultTwo2");// // error field does not exist on type
}
genericFunction<MethodKey extends string, ResultKey extends keyof MyClass>(method:()=> Observable<MyClass[ResultKey]>, result: ResultKey){
method().subscribe(
res => this[result] = res,
err => {}
);
}
}
try by using the following code:
private firstFunction () {
let response= genericFunction(this.serviceOne.methodOne())
}
private secondFunction () {
let response = genericFunction(this.serviceTwo.methodTwo())
}
Modify you Generic Function by just receiving a variable.
//if it is angular 4 or less
genericFunction (method: Observable) {
return method.map(res => {
return res.json();
});
}
//if it is angular 5 or 6
genericFunction (method: Observable) {
return method.pipe(
map(res => {
return res;
}));
}

Typescript: Property does not exist

I'm trying to develop a decorator for REST Api Interfaces in Typescript. Here it is the decorator implementation
export function RemoteResource(params: any): Function {
console.log("RemoteResource.params: ", params);
return function (target: Function) {
//--POST
target.prototype.post = function () {
console.log("----POST");
};
//--GET
target.prototype.retrieve = function () {
console.log("----GET");
};
//--DELETE
target.prototype.remove = function () {
console.log("----DELETE");
};
//--PULL
target.prototype.update = function () {
console.log("----PULL");
};
console.log("RemoteResource.target: ", target);
return target;
}
}
Now, I can use the decorator #RemoteResource and the methods post|retrieve|remove|update are added to the original object prototype correctly.
#RemoteResource({
path: "/foos",
methods: [],
requireAuth: false
})
export class Foo { }
From here, if I execute
let tester = new Foo();
tester.post() //--This prints out "----POST" correctly
I've the log printed out correctly, but I've also have the following error: "Property 'post' does not exist on type 'Foo'."
While I understand why I'm having this error (Foo doesn't have any declared post property) I'm not sure about how to fix it.
Ideally, I would like that the TS compiler understand that the decorator extends the original object adding up those methods.
How can I achieve it? Any ideas?
Thanks!
Since you are adding these methods dynamically at runtime in the decorator, the compiler has no way of knowing that these methods will exist for Foo instances.
You can change that in different ways, for example:
(1) Using an interface and intersection:
interface RemoteResource {
post(): void;
remove(): void;
update(): void;
retrieve(): void;
}
let tester = new Foo() as Foo & RemoteResource;
tester.post(); // no error
(2) Interface and empty methods:
export class Foo implements RemoteResource {
post: () => void;
remove: () => void;
update: () => void;
retrieve: () => void;
}
let tester = new Foo() as Foo & RemoteResource;
tester.post();
Edit
#Robba suggests:
(3) Ignore all type checking
let tester = new Foo() as any;
tester.post();
or
let tester = new Foo();
tester["post"]();

angular + restangular + typescript decorators error

I'm using:
angular 1.4
restangular for models
typescript as the language
This is my code:
function plain(){
return (target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) => {
var originalMethod = descriptor.value; // save a reference to the original method
descriptor.value = function(...args: any[]) {
var result = originalMethod.apply(this, args);
return result.then(function stripRestangularAttributes(response){
return response.plain();
});
};
return descriptor;
};
}
export class XYZ {
#plain
public getSomethingBySomething(data: Data): restangular.IPromise<any> {
if (!entityKey && !period) {
return null;
}
return this.restangularElement.all("route1/route2").post(data);
}
}
I get following error:
error TS1241: Unable to resolve signature of method decorator when called as an expression.
Supplied parameters do not match any signature of call target.
It is thrown on the #plain line.
Some info:
Restangular methods, .post(data) in this example, return promises
I want to:
use a typescript method decorator to chain a single element in the promise chain. The chain element is a thenable - something that will call .plain() on restangular results, so that I would just use #plain on any method and all results will get plain()-ed automagically.
in other words, when restangular returns a promise (as it always does), I want to chain following to each method that has #plain decorator attached: .then(function stripRestangularAttributes(response){
return response.plain();
})
Unfortunately, I can't get what is typescript complaining about in above example.
PS I've been reading this fantastic answer as my TS decorators guide.
Your method decorator shouldn't return a function, according to the spec:
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
Try this instead:
function plain(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) {
var originalMethod = descriptor.value; // save a reference to the original method
descriptor.value = function(...args: any[]) {
var result = originalMethod.apply(this, args);
return result.then(function stripRestangularAttributes(response){
return response.plain();
});
};
return descriptor;
};

Categories

Resources