The graph testing

Hi,

I got couple of issues that could be turned into features or they might be already implemented and I don’t know about it.

  1. my mapping calls supportsInterface on an address that looks like this.
let contract = ERC721.bind(addressHere);
let result = ethereum.call(
    new ethereum.SmartContractCall(
      contract._name, // '',
      contract._address, // address,
      'supportsInterface', // '',
      'supportsInterface(bytes4):(bool)',
      [ethereum.Value.fromFixedBytes(Bytes.fromHexString('0x80ac58cd') as Bytes)]
    )
  );

  if(result != null &&
    (result as Array<ethereum.Value>)[0].toBoolean() == true)) {
    // do something
  }

when supportsInterface is called from mapping, and the function isn’t mocked in test,
it fails with: couldn’t find mocked function. why ? can’t we have supportsInterface so even if mocked function
not exists, it doesn’t fail ? and more importantly, if supportsInterface is called on a non-erc721, would the realtime subgraph fail or is this happening only in test ? I don’t want to be mocking the supportsInterface in test and I simply want it to return null.

  1. The second problem I found is one test depens on another. If I mock supportsInterface in test 1, it’s also mocked in test 2 even though I didn’t mock it in test 2.
it("1", () => {
   // I create mocked function here
})

it("2", () => {
   // problem, the mocked function is still in cache in test 2
   // and this is really bad.
})

is there clearCache function ? and if so, should I be calling it in afterEach ? still not ideal.

  1. I got 5 tests written. Each of them tests something about ERC721. I end up having the following in all 5 tests.
// check ERC721Contract entity
    assert.fieldEquals('ERC721Contract', tokenId, 'id', tokenId);
    assert.fieldEquals('ERC721Contract', tokenId, 'name', 'name');
    assert.fieldEquals('ERC721Contract', tokenId, 'symbol', 'symbol');
    assert.entityCount('ERC721Contract', 1);

    // check ERC721Balance entity
    assert.fieldEquals('ERC721Balance', balanceId, 'id', balanceId);
    assert.fieldEquals('ERC721Balance', balanceId, 'token', tokenId);
    assert.fieldEquals('ERC721Balance', balanceId, 'dao', daoId);
    assert.fieldEquals('ERC721Balance', balanceId, 'tokenIds', '[4, 12]');
    assert.fieldEquals('ERC721Balance', balanceId, 'lastUpdated', timestamp.toString());
    assert.entityCount('ERC721Balance', 1);

The reason I wanted the feature of having let variables in describe block is, then I’d be able to have the chance that each test just sets the variables (tokenId, balanceId, daoId, timestamp), and the above assertions would go in afterEach and wouldn’t be duplicated. Isn’t there really a solution for this ? If I create assertionFunction that includes the above, It’s not ideal.

Thank you so much

1 Like

Hi @jordangio, great questions. I’ll try to give some answers here:

  1. I think it is the nature of Unit-testing that every function needs to be mocked. That said, you (or someone else in the community) could write a library that already has mocks included for the most common functions.
    1.1. If a contract does not support a call, the call would revert. It is possible to handle that case with the try_-prefix. Learn more about it here: AssemblyScript API - The Graph Docs.
  2. This looks like a feature request to me. Do you mind opening an issue here: GitHub - LimeChain/matchstick: 🔥 Unit testing framework for Subgraph development on The Graph protocol. ⚙️?
  3. I’d write a helper function for this.
1 Like

Thanks for gettin back to me.

  1. It actually doesn’t make subgraph(on main network) fail if supportsInterface doesn’t exist on the contract. Why do you mention that it will fail in (1.1.) bullet point ? I tested and it doesn’t revert. It just returns null.
1 Like

Oh, right, that makes sense. I was not very familiar with the way you did the eth-call with the low-level ethereum.call. The following code could be written more concise:

let contract = ERC721.bind(addressHere);
let result = ethereum.call(
    new ethereum.SmartContractCall(
      contract._name, // '',
      contract._address, // address,
      'supportsInterface', // '',
      'supportsInterface(bytes4):(bool)',
      [ethereum.Value.fromFixedBytes(Bytes.fromHexString('0x80ac58cd') as Bytes)]
    )
  );

  if(result != null &&
    (result as Array<ethereum.Value>)[0].toBoolean() == true)) {
    // do something
  }

Shorter version:

let contract = ERC721.bind(addressHere);
let result = conctract.try_supportsInterface(Bytes.fromHexString('0x80ac58cd') as Bytes);

if (!result.reverted && result.value == true) {
 // do something
}

The second line could be written without the try_-prefix like:

let result = conctract.supportsInterface(Bytes.fromHexString('0x80ac58cd') as Bytes);

A subgraph with this would fail if the contract does not have the supportsInterface call implemented.

1 Like