Skip to content

Why does mocha chai should not prove identity for `return this`, if I use Proxy:get in the constructor?

I want to write a class, that deals with undefined properties. I also want to return this to be able to chain methods to create a domain specific language (DSL).

I return a Proxy from the constructor, to handle undefined properties. Now when testing the instance, it does happen, that return this does not prove to be identical with the instance. I fear bugs resulting from this, although I can chain the methods as intended.

This is a mocha chai test to show the behaviour. Replace o.that().should.not.equal(o); with o.that().should.equal(o); in the last instruction to see how it fails.

require('chai').should();

describe('chai testing classes using Proxy', () => {
    it('asserts object identity, if an instance method returns this', () => {
        const o = new class NormalClass{ }
        o.that = function() { return this; }
        o.that().should.equal(o);
    });
    it('observes the same behaviour for constructors returning a dummy Proxy', () => {
        const o = new class ProxyClass{
            constructor() { return new Proxy(this, {}); }
        }
        o.that = function() { return this; }
        o.that().should.equal(o);
    });
    it('requires deep.equal on the other hand, if the Proxy handles get', () => {
        const o = new class ProxyClassPlusGet{
            constructor() {
                return new Proxy(this, {
                    get: function(target, prop) { return target[prop]; },
                });
            }
        }
        o.that = function() { return this; }
        o.that().should.deep.equal(o);
        o.that().should.not.equal(o);
    });
});

Answer

Your implementation works insofar as o.that() === o yields true.

But it does not work with getters, which interferes with chai’s should. You can reproduce this with

const o = new Proxy({
    get self() { return this; },
    that() { return this; },
}, {
    get(target, prop) { return target[prop]; },
});
console.log(o.self === o);
console.log(o.that() === o);

Why is that? Because your get trap is broken, ignoring the receiver of the property access. It will hold the proxy, o, but when you do return target[prop] then target will be the receiver. You can fix it by using Reflect.get:

const o = new Proxy({
    get self() { return this; },
    that() { return this; },
}, {
    get(target, prop, receiver) {
        return Reflect.get(target, prop, receiver);
//                                       ^^^^^^^^
    },
});
console.log(o.self === o);
console.log(o.that() === o);