srinesha
srinesha

Reputation: 23

Mocking an async builder pattern API with proxy

I'm trying to mock knex for testing. Everything seems to work but attaching a proxy to an array as prototype seems to remove the iteratability of arrays.

Here is the mock function. Following works fine with objects.

const mock = (data) => {
    const orgData = structuredClone(data)

    Object.setPrototypeOf(
        data,
        new Proxy(
            {},
            {
                get(_, prop) {
                    if (prop === 'then') {
                        return orgData
                    }

                    if (typeof prop === 'symbol') {
                        return orgData
                    }

                    return () => mock(orgData)
                },
            }
        )
    )

    return data
}
const mm = mock([
    {
        name: 'user',
    },
])

const run = async () => {
    const res = await mm.select('*').where('name', 'user')
    const [first] = res
    //             ^^^^^
    // TypeError: res is not iterable
    //     at run (.../test.js:35:18)
}
run()

When there is await present before the chain, proxy get is called with then as the value of prop. And that's the last call. That returns [ { name: 'user' } ] correctly. However the value that's assigned to the res variable is looks like this { '0': { name: 'user' } }. I'm wondering if it's possible to assign a prototype to an array without making it an object.

Upvotes: 0

Views: 63

Answers (1)

s1n7ax
s1n7ax

Reputation: 3069

I'm the same guy who posted this question. The resolution was not to set the prototype at all and instead, directly proxy the promise itself.

const mock = (data) => {
      return new Proxy(Promise.resolve(data), {
            get(target, prop) {
                  if (prop in target) {
                        const promise = Promise.resolve(target)
                        // NOTE: when `then` is called independently, this will be global so it
                        // wouldn't be resolved correctly. So here binding the context to the
                        // function to make it work
                        return promise.then.bind(promise)
                  }

                  return () => mock(target)
            },
      })
}

const mm = mock([
      {
            name: 'user',
      },
])

const run = async () => {
      const res = await await mm.select('*').where('name', 'user')
      console.log(res)
      const [first] = res
      console.log(first)
}

run()

Output:

:!node test.js
[ { name: 'user' } ]
{ name: 'user' }

Upvotes: 0

Related Questions