DePayFi / web3-mock

🤡 JavaScript library to mock web3 responses either by emulating web3 wallets or web3 RPC requests.
https://depay.com
MIT License
87 stars 20 forks source link

mock function returned object is "not a spy or a call to a spy" #19

Closed olifur closed 2 years ago

olifur commented 2 years ago

Hi!

On @depay/web3-mock@11.11.0 and also on 12.x I have the following problem:

When I run the following code using cypress (taken and slightly modified from the Advanced demo)

import { mock, resetMocks } from '@depay/web3-mock'

describe('something', ()=> {

  let blockchain = 'ethereum'
  let accounts = ['0xd8da6bf26964af9d7eed9e03e53415d37aa96045']
  beforeEach(resetMocks)
  beforeEach(()=>mock({ blockchain, accounts: { return: accounts } }))

  it('mocks contract calls', ()=>{

    let balanceOfMock = mock({
      blockchain,
      call: {
        to: '0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb',
        api: [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burnBeforeRelease","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"extension","type":"uint256"}],"name":"extendVestingPeriod","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastVestingRelease","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"releasable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"release","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vestingBeneficial","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vestingPeriodEnd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vestingRewardPerSecond","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}],
        method: 'balanceOf',
        params: "0x5Af489c8786A018EC4814194dC8048be1007e390",
        return: "1000000000000000000"
      }
    })

    //await token.balanceOf("0x5Af489c8786A018EC4814194dC8048be1007e390") // "1000000000000000000"

    expect(balanceOfMock).to.have.been.called
    expect(balanceOfMock).to.have.been.called.once
  })
})

I get the following problem when I am running it in cypress

cypress-firefox_1  |   something
cypress-firefox_1  |     1) mocks contract calls
cypress-firefox_1  | 
cypress-firefox_1  | 
cypress-firefox_1  |   0 passing (216ms)
cypress-firefox_1  |   1 failing
cypress-firefox_1  | 
cypress-firefox_1  |   1) something
cypress-firefox_1  |        mocks contract calls:
cypress-firefox_1  |      TypeError: { blockchain: 'ethereum',
cypress-firefox_1  |   call: 
cypress-firefox_1  |    { to: '0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb',
cypress-firefox_1  |      api: 
cypress-firefox_1  |       [ [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object],
cypress-firefox_1  |         [Object] ],
cypress-firefox_1  |      method: 'balanceOf',
cypress-firefox_1  |      params: '0x5Af489c8786A018EC4814194dC8048be1007e390',
cypress-firefox_1  |      return: '1000000000000000000' },
cypress-firefox_1  |   calls: 
cypress-firefox_1  |    { add: [Function: add],
cypress-firefox_1  |      all: [Function: all],
cypress-firefox_1  |      count: [Function: count] } } is not a spy or a call to a spy!
cypress-firefox_1  |   assertCanWorkWith@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:1499:19
cypress-firefox_1  |   ../../node_modules/@cypress/sinon-chai/lib/sinon-chai.js/</sinonProperty/<@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:1538:30
cypress-firefox_1  |   propertyGetter@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:96302:29
cypress-firefox_1  |   proxyGetter@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:97740:22
cypress-firefox_1  |   ./cypress/e2e/walletconnect.test.cy.ts/</<@http://172.17.0.1:3001/__cypress/tests?p=cypress/e2e/walletconnect.test.cy.ts:3504:15
cypress-firefox_1  |   ../driver/src/cypress/cy.ts/setRunnable/runnable.fn@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:171945:43
cypress-firefox_1  |   callFn@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:122317:21
cypress-firefox_1  |   ../driver/node_modules/mocha/lib/runnable.js/</Runnable.prototype.run@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:122304:13
cypress-firefox_1  |   ../driver/src/cypress/runner.ts/create/onRunnableRun/<@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:179091:30
cypress-firefox_1  |   finallyHandler@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:9566:23
cypress-firefox_1  |   tryCatcher@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:13012:23
cypress-firefox_1  |   ../../node_modules/bluebird/js/release/promise.js/</module.exports/Promise.prototype._settlePromiseFromHandler@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:10947:31
cypress-firefox_1  |   ../../node_modules/bluebird/js/release/promise.js/</module.exports/Promise.prototype._settlePromise@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:11004:18
cypress-firefox_1  |   ../../node_modules/bluebird/js/release/promise.js/</module.exports/Promise.prototype._settlePromise0@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:11049:10
cypress-firefox_1  |   ../../node_modules/bluebird/js/release/promise.js/</module.exports/Promise.prototype._settlePromises@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:11129:18
cypress-firefox_1  |   _drainQueueStep@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:7719:12
cypress-firefox_1  |   _drainQueue@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:7712:24
cypress-firefox_1  |   ../../node_modules/bluebird/js/release/async.js/</Async.prototype._drainQueues@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:7728:16
cypress-firefox_1  |   ../../node_modules/bluebird/js/release/async.js/</Async/this.drainQueues@http://172.17.0.1:3001/__cypress/runner/cypress_runner.js:7598:14

a console.log(balanceOfMock) suggests that it is a normal {} object and not a function.

olifur commented 2 years ago

looks to me like mockBlockchain function return object gets wrapped by a spy https://github.com/DePayFi/web3-mock/blob/797ebec6eab37a0ee65d2b08cba84135bb93d270/src/mock.js#L106 but mock function missed the spy wrap https://github.com/DePayFi/web3-mock/blob/797ebec6eab37a0ee65d2b08cba84135bb93d270/src/mock.js#L131

10xSebastian commented 2 years ago

return mock is the mock created by mockBlockchain.

mockBlockchain is the only occasion for the mock variable getting set: see https://github.com/DePayFi/web3-mock/blob/797ebec6eab37a0ee65d2b08cba84135bb93d270/src/mock.js#L125

10xSebastian commented 2 years ago

web3-mock currently only implements the jest spy attribute calls (as you can see in your error message it's right there)...

https://jestjs.io/docs/expect#tohavebeencalled

10xSebastian commented 2 years ago

what is Cypress requirement for the to.have.been.called matcher? can you point to the implementation of this matcher?

olifur commented 2 years ago

Cypress 10.x ships with chai, as the default assertion library:

https://docs.cypress.io/guides/references/assertions

Chai assertions

https://www.chaijs.com/api/bdd/

10xSebastian commented 2 years ago

I've just released 13.7.0 which is fixing cypress matcher/assertions

https://github.com/DePayFi/web3-mock/releases/tag/v13.7.0

Make sure you migrate to the 12+ syntax to mock requests (it's mock({request: ...}) and not mock({call: ...}) anymore).

btw. Cypress assertions/matchers do look like this: https://docs.cypress.io/guides/references/assertions#Sinon-Chai not like pure chai BDD