I'm trying to make a subclass of an image library on github called Jimp. As far as I can tell from the docs, you don't instantiate the class in the usual way. Instead of saying new Jimp(), it seems the class has a static method called read that acts as a constructor. From the docs...
Jimp.read("./path/to/image.jpg").then(function (image) {
// do stuff with the image
}).catch(function (err) {
// handle an exception
});
It looks like from the docs, that that image returned by read() is an instance allowing the caller to do stuff like image.resize( w, h[, mode] ); and so on.
I'd like to allow my subclass callers to begin with a different static method that reads an image and does a bunch of stuff, summarized as follows...
class MyJimpSubclass extends Jimp {
static makeAnImageAndDoSomeStuff(params) {
let image = null;
// read in a blank image and change it
return Jimp.read("./lib/base.png").then(_image => {
console.log(`image is ${_image}`);
image = _image;
let foo = image.bar(); // PROBLEM!
// ...
// ...
.then(() => image);
}
bar() {
// an instance method I wish to add to the subclass
}
// caller
MyJimpSubclass.makeAnImageAndDoSomeStuff(params).then(image => {
//...
});
You might be able to guess that nodejs gets angry on the line let foo = image.bar();, saying
TypeError image.bar is not a function
.
I think this is understandable, because I got that image using Jimp.read(). Of course that won't return an instance of my subclass.
First idea: Change it to MyJimpSubclass.read(). Same problem.
Second idea: Implement my own static read method. Same problem.
static read(params) {
return super.read(params);
}
Third idea: Ask SO
The implementation of Jimp.read refers to Jimp specifically, so you would have to copy and change it in your subclass (ick, but not going to break anything since the constructor is also part of the API) or make a pull request to have it changed to this and have subclassing explicitly supported:
static read(src) {
return new Promise((resolve, reject) => {
void new this(src, (err, image) => {
if (err) reject(err);
else resolve(image);
});
});
}
Alternatively, you could just implement all your functionality as a set of functions on a module. This would be next on my list after making a pull request. Would not recommend a proxy.
const makeAnImageAndDoSomeStuff = (params) =>
Jimp.read("./lib/base.png").then(image => {
console.log(`image is ${image}`);
let foo = bar(image);
// …
return image;
});
function bar(image) {
// …
}
module.exports = {
makeAnImageAndDoSomeStuff,
bar,
};
Even changing the prototype would be better than a proxy (but this is just a worse version of the first option, reimplementing read):
static read(src) {
return super.read(src)
.then(image => {
Object.setPrototypeOf(image, this.prototype);
return image;
});
}
You have a couple of options. The cleanest is probably to make a subclass like you started, but then implement the Jimp static method on it, as well as your own. In this case, it's not really inheritance, so don't use extends.
class MyJimp {
static read(...args) {
return Jimp.read.apply(Jimp, args);
}
static makeAnImage(params) {
return this.read(params)
.then(image => {
// do stuff
return image
});
}
}
From there, I would make an object which has all of the new functions you want to apply to image:
const JimpImageExtension = {
bar: () => { /* do something */ }
};
Finally, in your static methods, get the image and use Object.assign() to apply your new functions to it:
class MyJimp {
static read(...args) {
return Jimp.read.apply(Jimp, args)
.then(image => Object.assign(image, JimpImageExtension));
}
static makeAnImage(params) {
return this.read(params)
.then(image => {
// do stuff
image.bar();
return image;
});
}
}
This should do the trick by applying your extra functions to the image. You just need to make sure that you apply it at every point that can generate an image (if there is more than just read). Since in the other functions, it's using your version of read(), you only need to add the functions in the one.
Another approach would be if Jimp makes their image class accessible, you could also add them to the prototype of that (though usually in libraries like this, that class is frequently inaccessible or not actually a class at all).
This might be a way to do it. Start with your own read method, and have it change the prototype of the returned object.
static read(...params) {
return super.read(...params).then(image) {
image.prototype = MyJimpSubclass;
resolve(image);
}
}
Related
I'm trying to stub a function using sinon. The function has the following signature
export function getIndexDocument(
svc: MetaHTTPService | ServiceConfig
): MetaPromise<RepoResponseResult<IndexDocument>> {
Is this the right way to sub it
sandbox.stub(getIndexDocument).resolves({} as RepoResponseResult)
I tried that but it returns an error.
Here's how this function is called.
I have a class called AssetsController with the following functions
public async exploreIndexDocument(): Promise<Asset | undefined> {
// it makes an HTTP request and returns a promise that resolves with the following info { repoId: "", assetId: "" }
const {
result: { assignedDirectories }
} = await getIndexDocument(this.serviceConfig).catch(err => {
throw new Error(`Bad repsonse`);
});
return {
repoId: result.repoId;
assetId: result.assetId
}
}
public async function copyAsset(asset) {
const res = await this.exploreIndexDocument();
const repoId = res.repoId;
return asset.copy(repoId);
}
I'm trying to test the function copyAsset, but it calls exploreIndexDocument which calls getIndexDocument. getIndexDocument is imported at the top of the file and lives in the module #ma/http.
getIndexDocument makes an HTTP request.
How can I test copyAsset given that it calls getIndexDocument which makes an HTTP request?
According to the docs, you can't stub an existing function.
You can:
// Create an anonymous sstub function
var stub = sinon.stub();
// Replaces object.method with a stub function. An exception is thrown
// if the property is not already a function.
var stub = sinon.stub(object, "method");
// Stubs all the object’s methods.
var stub = sinon.stub(obj);
What you can't do is stub just a function like:
var stub = sinon.stub(myFunctionHere);
This makes sense because if all you have is a reference to a function, then you can just create a new function to use instead, and then pass that into where ever your test needs it to go.
I think you just want:
const myStub = sandbox.stub().resolves({} as RepoResponseResult)
In your update it sounds like you want to put the stub on the AssetsController class. See this answer for more info on that, but in this case I think you want:
const myStub = sandbox
.stub(AssetsController.prototype, 'exploreIndexDocument')
.resolves({} as RepoResponseResult)
Now anytime an instance of AssetsController calls its exploreIndexDocument method, the stub should be used instead.
Playground
I think most of your problems can be solved by revisiting your architecture. For example, instead of creating an explicit dependency on getIndexDocument within your AssetController class you can simply inject it in. This will allow you to swap implementations depending on the context.
type IndexDocumentProvider = (svc: MetaHTTPService | ServiceConfig) => MetaPromise<RepoResponseResult<IndexDocument>>;
interface AssetControllerOptions {
indexDocumentProvider: IndexDocumentProvider
}
class AssetController {
private _getIndexDocument: IndexDocumentProvider;
public constructor(options: AssetControllerOptions) {
this._getIndexDocument = options.indexDocumentProvider;
}
}
Then you can use this._getIndexDocument wherever and not worry about how to make the original implementation behave like you want in your tests. You can simply provide an implementation that does whatever you'd like.
describe('copyAsset', () => {
it('fails on index document error.', () => {
const controller = new AssetController({
indexDocumentProvider: () => Promise.reject(new Error(':('));
});
....
});
it('copies asset using repo id.', () => {
const controller = new AssetController({
indexDocumentProvider: () => Promise.resolve({ repoId: "420" })
});
...
});
});
You can obviously use stubs instead of just functions or whatever if you need something fancy.
Above we removed an explicit dependency to an implementation and instead replaced it with a contract that must be provided to the controller. The is typically called Inversion of Control and Dependency Injection
What I was trying to accomplish. I wanted to share a single canvas (because what I'm doing is very heavy) and so I thought I'd make a limited resource manager. You'd ask it for the resource via promise, in this case a Canvas2DRenderingContext. It would wrap the context in a revokable proxy. When you're finished you are required to call release which both returns the canvas to the limited resource manager so it can give it to someone else AND it revokes the proxy so the user can't accidentally use the resource again.
Except when I make a proxy of a Canvas2DRenderingContext it fails.
const ctx = document.createElement('canvas').getContext('2d');
const proxy = new Proxy(ctx, {});
// try to change the width of the canvas via the proxy
test(() => { proxy.canvas.width = 100; }); // ERROR
// try to translate the origin of via the proxy
test(() => { proxy.translate(1, 2); }); // ERROR
function test(fn) {
try {
fn();
} catch (e) {
console.log("FAILED:", e, fn);
}
}
The code above generates Uncaught TypeError: Illegal invocation in Chrome and TypeError: 'get canvas' called on an object that does not implement interface CanvasRenderingContext2D. in Firefox
Is that an expected limitation of Proxy or is it a bug?
note: of course there are other solutions. I can remove the proxy and just not worry about it. I can also wrap the canvas in some JavaScript object that just exposes the functions I need and proxy that. I'm just more curious if this is supposed to work or not. This Mozilla blog post kind of indirectly suggests it's supposed to be possbile since it actually mentions using a proxy with an HTMLElement if only to point out it would certainly fail if you called someElement.appendChild(proxiedElement) but given the simple code above I'd expect it's actually not possible to meanfully wrap any DOM elements or other native objects.
Below is proof that Proxies work with plain JS objects. They work with class based (as in the functions are on the prototype chain). And they don't work with native objects.
const img = document.createElement('img')
const proxy = new Proxy(img, {});
console.log(proxy.src);
Also fails with the same error. where as they don't with JavaScript objects
function testNoOpProxy(obj, msg) {
log(msg, '------');
const proxy = new Proxy(obj, {});
check("get property:", () => proxy.width);
check("set property:", () => proxy.width = 456);
check("get property:", () => proxy.width);
check("call fn on object:", () => proxy.getContext('2d'));
}
function check(msg, fn) {
let success = true;
let r;
try {
r = fn();
} catch (e) {
success = false;
}
log(' ', success ? "pass" : "FAIL", msg, r, fn);
}
const test = {
width: 123,
getContext: function() {
return "test";
},
};
class Test {
constructor() {
this.width = 123;
}
getContext() {
return `Test width = ${this.width}`;
}
}
const testInst = new Test();
const canvas = document.createElement('canvas');
testNoOpProxy(test, 'plain object');
testNoOpProxy(testInst, 'class object');
testNoOpProxy(canvas, 'native object');
function log(...args) {
const elem = document.createElement('pre');
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
pre { margin: 0; }
Well FWIW the solution I choose was to wrap the canvas in a small class that does the thing I was using it for. Advantage is it's easier to test (since I can pass in a mock) and I can proxy that object no problem. Still, I'd like to know
Why doesn't Proxy work for native object?
Do any of the reasons Proxy doesn't work with native objects apply to situations with JavaScript objects?
Is it possible to get Proxy to work with native objects.
const handlers = {
get: (target, key) => key in target ? target[key] : undefined,
set: (target, key, value) => {
if (key in target) {
target[key] = value;
}
return value;
}
};
const { revoke, proxy } = Proxy.revocable(ctx, handlers);
// elsewhere
try {
proxy.canvas.width = 500;
} catch (e) { console.log("Access has been revoked", e); }
Something like that should do what you're expecting.
A revocable proxy, with handlers for get and set traps, for the context.
Just keep in mind that when an instance of Proxy.revocable() is revoked, any subsequent access of that proxy will throw, and thus everything now needs to use try/catch, in the case that it has, indeed, been revoked.
Just for fun, here's how you can do the exact same thing without fear of throwing (in terms of simply using the accessor; no guarantee for doing something wrong while you still have access):
const RevocableAccess = (item, revoked = false) => ({
access: f => revoked ? undefined : f(item),
revoke: () => { revoked = true; }
});
const { revoke, access: useContext } = RevocableAccess(ctx);
useContext(ctx => ctx.canvas.width = 500);
revoke();
useContext(ctx => ctx.canvas.width = 200); // never fires
Edit
As pointed out in the comments below, I completely neglected to test for the method calls on the host object, which, it turns out, are all protected. This comes down to weirdness in the host objects, which get to play by their own rules.
With a proxy as above, proxy.drawImage.apply(ctx, args) would work just fine.
This, however, is counter-intuitive.
Cases that I'm assuming fail here, are Canvas, Image, Audio, Video, Promise (for instance based methods) and the like. I haven't conferred with the spec on this part of Proxies, and whether this is a property-descriptor thing, or a host-bindings thing, but I'm going to assume that it's the latter, if not both.
That said, you should be able to override it with the following change:
const { proxy, revoke } = Proxy.revocable(ctx, {
get(object, key) {
if (!(key in object)) {
return undefined;
}
const value = object[key];
return typeof value === "function"
? (...args) => value.apply(object, args)
: value;
}
});
Here, I am still "getting" the method off of the original object, to call it.
It just so happens that in the case of the value being a function, I call bind to return a function that maintains the this relationship to the original context. Proxies usually handle this common JS issue.
...this causes its own security concern; someone could cache the value out, now, and have permanent access to, say, drawImage, by saying
const draw = proxy.drawImage;...
Then again, they already had the ability to save the real render context, just by saying
const ctx = proxy.canvas.getContext("2d");
...so I'm assuming some level of good-faith, here.
For a more secure solution, there are other fixes, though with canvas, unless it's in-memory only, the context is ultimately going to be available to anyone who can read the DOM.
With JavaScript, when creating a class, while instantiating that class, I want to populate a public property. I can do this using a setter, however, the value I want comes from an external website which I retrieve via an ajax get call. The issue becomes that the new class object does not have the appropriate property value when I create it.
Here's some sample code:
class MyTestClass {
constructor() {
this._ipaddress = "";
}
get ip() {
return this._ipaddress;
}
set ip(value) {
createIpAddr();
}
createIpAddr() {
var myIpAddr = "";
var strUrl = "https://api.ipify.org/?format=json";
$.ajax({
url: strUrl,
success: function(data) {
this._ip = data.ip;
},
//async:false //Performing this in sync mode to make sure I get an IP before the rest of the page loads.
});
return myIpAddr;
}
}
var testobj = new MyTestClass();
console.log(testobj.ip);
The problem here is that I can't be sure the IP will be populated in time to use after creating the new instance of the class. I've tried promises and deffered, but they have the same problem, I can't populate the variable before I need it. I'm trying to adjust the way I am looking at this and adding callbacks, but the issue is that I need the correct value in the class before I can use the class for the next call, where I am passing this object to it.
Is there a simple solution I am over looking? I have been through a million of these threads about async: false, and I don't want to start a new one, but what is a better choice in this case?
I want to set a class property from an ajax response when instantiating the class object.
You could have your constructor return an async IIFE, allowing you to then await the creation of a new class instance.
That would look something like this:
class MyTestClassAsync {
constructor() {
return (async() => {
this._ip = (await this.createIpAddrAsync()).ip;
return this;
})();
}
get ip() {
return this._ip;
}
set ip(value) {
this._ip = value;
}
createIpAddrAsync = () => $.get("https://api.ipify.org/?format=json");
}
async function Main() {
var testobj = await new MyTestClassAsync();
console.log(testobj.ip);
}
Main();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I've made the personal decision to append "Async" to the method and class names, just so that it's clear they need to be awaited.
I'm trying to mock an ES6 class with a constructor that receives parameters, and then mock different class functions on the class to continue with testing, using Jest.
Problem is I can't find any documents on how to approach this problem. I've already seen this post, but it doesn't resolve my problem, because the OP in fact didn't even need to mock the class! The other answer in that post also doesn't elaborate at all, doesn't point to any documentation online and will not lead to reproduceable knowledge, since it's just a block of code.
So say I have the following class:
//socket.js;
module.exports = class Socket extends EventEmitter {
constructor(id, password) {
super();
this.id = id;
this.password = password;
this.state = constants.socket.INITIALIZING;
}
connect() {
// Well this connects and so on...
}
};
//__tests__/socket.js
jest.mock('./../socket');
const Socket = require('./../socket');
const socket = new Socket(1, 'password');
expect(Socket).toHaveBeenCalledTimes(1);
socket.connect()
expect(Socket.mock.calls[0][1]).toBe(1);
expect(Socket.mock.calls[0][2]).toBe('password');
As obvious, the way I'm trying to mock Socket and the class function connect on it is wrong, but I can't find the right way to do so.
Please explain, in your answer, the logical steps you make to mock this and why each of them is necessary + provide external links to Jest official docs if possible!
Thanks for the help!
Update:
All this info and more has now been added to the Jest docs in a new guide, "ES6 Class Mocks."
Full disclosure: I wrote it. :-)
The key to mocking ES6 classes is knowing that an ES6 class is a function. Therefore, the mock must also be a function.
Call jest.mock('./mocked-class.js');, and also import './mocked-class.js'.
For any class methods you want to track calls to, create a variable that points to a mock function, like this: const mockedMethod = jest.fn();. Use those in the next step.
Call MockedClass.mockImplementation(). Pass in an arrow function that returns an object containing any mocked methods, each set to its own mock function (created in step 2).
The same thing can be done using manual mocks (__mocks__ folder) to mock ES6 classes. In this case, the exported mock is created by calling jest.fn().mockImplementation(), with the same argument described in (3) above. This creates a mock function. In this case, you'll also need to export any mocked methods you want to spy on.
The same thing can be done by calling jest.mock('mocked-class.js', factoryFunction), where factoryFunction is again the same argument passed in 3 and 4 above.
An example is worth a thousand words, so here's the code.
Also, there's a repo demonstrating all of this, here:
https://github.com/jonathan-stone/jest-es6-classes-demo/tree/mocks-working
First, for your code
if you were to add the following setup code, your tests should pass:
const connectMock = jest.fn(); // Lets you check if `connect()` was called, if you want
Socket.mockImplementation(() => {
return {
connect: connectMock
};
});
(Note, in your code: Socket.mock.calls[0][1] should be [0][0], and [0][2] should be [0][1]. )
Next, a contrived example
with some explanation inline.
mocked-class.js. Note, this code is never called during the test.
export default class MockedClass {
constructor() {
console.log('Constructed');
}
mockedMethod() {
console.log('Called mockedMethod');
}
}
mocked-class-consumer.js. This class creates an object using the mocked class. We want it to create a mocked version instead of the real thing.
import MockedClass from './mocked-class';
export default class MockedClassConsumer {
constructor() {
this.mockedClassInstance = new MockedClass('yo');
this.mockedClassInstance.mockedMethod('bro');
}
}
mocked-class-consumer.test.js - the test:
import MockedClassConsumer from './mocked-class-consumer';
import MockedClass from './mocked-class';
jest.mock('./mocked-class'); // Mocks the function that creates the class; replaces it with a function that returns undefined.
// console.log(MockedClass()); // logs 'undefined'
let mockedClassConsumer;
const mockedMethodImpl = jest.fn();
beforeAll(() => {
MockedClass.mockImplementation(() => {
// Replace the class-creation method with this mock version.
return {
mockedMethod: mockedMethodImpl // Populate the method with a reference to a mock created with jest.fn().
};
});
});
beforeEach(() => {
MockedClass.mockClear();
mockedMethodImpl.mockClear();
});
it('The MockedClassConsumer instance can be created', () => {
const mockedClassConsumer = new MockedClassConsumer();
// console.log(MockedClass()); // logs a jest-created object with a mockedMethod: property, because the mockImplementation has been set now.
expect(mockedClassConsumer).toBeTruthy();
});
it('We can check if the consumer called the class constructor', () => {
expect(MockedClass).not.toHaveBeenCalled(); // Ensure our mockClear() is clearing out previous calls to the constructor
const mockedClassConsumer = new MockedClassConsumer();
expect(MockedClass).toHaveBeenCalled(); // Constructor has been called
expect(MockedClass.mock.calls[0][0]).toEqual('yo'); // ... with the string 'yo'
});
it('We can check if the consumer called a method on the class instance', () => {
const mockedClassConsumer = new MockedClassConsumer();
expect(mockedMethodImpl).toHaveBeenCalledWith('bro');
// Checking for method call using the stored reference to the mock function
// It would be nice if there were a way to do this directly from MockedClass.mock
});
For me this kind of Replacing Real Class with mocked one worked.
// Content of real.test.ts
jest.mock("../RealClass", () => {
const mockedModule = jest.requireActual(
"../test/__mocks__/RealClass"
);
return {
...mockedModule,
};
});
var codeTest = require("../real");
it("test-real", async () => {
let result = await codeTest.handler();
expect(result).toMatch(/mocked.thing/);
});
// Content of real.ts
import {RealClass} from "../RealClass";
export const handler = {
let rc = new RealClass({doing:'something'});
return rc.realMethod("myWord");
}
// Content of ../RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "The.real.deal "+input;
}
// Content of ../test/__mocks__/RealClass.ts
export class RealClass {
constructor(something: string) {}
async realMethod(input:string) {
return "mocked.thing "+input;
}
Sorry if I misspelled something, but I'm writing it on the fly.
As mentioned in the interface section of 'Programming javascript applications' you can implement interfaces with stampit. When reducing the unnecessary stuff from the example, I end up with something like this:
const fooBarInterface =
stampit()
.methods(
{
foo: () => {
throw new Error('connect not implemented');
},
bar: () => {
throw new Error('save not implemented');
}
}
)
Excerpt from an interface definition:
If your class claims to implement an interface, all methods defined by
that interface must appear in its source code before the class will
successfully compile.
So now putting the interface in use
const fooBarImplementation =
stampit()
.compose(fooBarInterface)
.methods(
{
foo: () => {
// implement me
}
}
}
)
Now, when composing an object from the stamp, there should be an error because bar is not implemented by fooBarImplementation. It isn't, and I fear that it's pretty hard to get something like this in place because there is no compiling at all.
So, my question is: Am I doing it wrong or is this half baked thing what Eric Elliott calls an 'Interface'?
The module you've created is great! You really know stampit well.
Although, in JavaScript I would recommend to go a different path. Namely, check if method is present.
if (obj.bar) kangaroo.bar();
And remove the fooBarInterface entirely. But if you need to check the method presence while creating the object, then you should do it similar to your module.
var ValidateFooBar = stampit()
.init(function() {
if (!_.isFunction(this.foo)) throw new Error('foo not implemented');
if (!_.isFunction(this.bar)) throw new Error('bar not implemented');
});
And use it:
const fooBarImplementation = stampit()
.compose(ValidateFooBar)
.methods({
foo: function() {
// implement me
}
});
Will throw: Error: bar not implemented