Skip to content

Commit

Permalink
feat: add explanation for testing subgraph extended classes (#564)
Browse files Browse the repository at this point in the history
* feat: add explanation for testing subgraph extended classes

* Update packages/subgraph/README.md

Co-authored-by: Jordan <[email protected]>

* Update packages/subgraph/README.md

Co-authored-by: Jordan <[email protected]>

* fix: prettier check

---------

Co-authored-by: Jordan <[email protected]>
  • Loading branch information
novaknole and jordaniza authored Apr 8, 2024
1 parent cd65ae1 commit 6b3f5f8
Showing 1 changed file with 111 additions and 0 deletions.
111 changes: 111 additions & 0 deletions packages/subgraph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,117 @@ To stop:
yarn stop:dev
```

### Writing the tests

In the tests, most of the time, one needs to do:

- Create an event object.
- Call the handler with this event object.
- Check whether the handler successfully created an entity with the values that came from the event.

The simple code could look like:

```js
let event = createNewProposalCreatedEvent(
PLUGIN_PROPOSAL_ID,
ADDRESS_ONE,
START_DATE,
END_DATE,
STRING_DATA,
[],
ALLOW_FAILURE_MAP,
CONTRACT_ADDRESS
);

_handleProposalCreated(event, daoEntityId, STRING_DATA);

assert.fieldEquals('AddresslistVotingProposal', PROPOSAL_ENTITY_ID, 'id', PROPOSAL_ENTITY_ID);
assert.fieldEquals('AddresslistVotingProposal', PROPOSAL_ENTITY_ID, 'dao', daoEntityId);
// ...more fieldEqual calls
```

As one can see, most of the time, event object would be created with some default values, such as in the example. i.e
ADDRESS_ONE, START_DATE, END_DATE,.... So prior to extended class approach, we ended up having default values in the tests themselves, and then calling `assert.fieldEquals` multiple times to check all the fields were stored successfully on the entity. Not only this caused code to become larger and larger, but default values were scattered throughout the whole test codebase. Even though, these default values could be hidden/abstracted away from the main test files, they still have to live somewhere and there's no appropriate files for them to be included in.

Aragon currently uses extended class approach where we dynamically and programatically create extended class that extends from the actual entity classes and append `Extended` to the name. For example: if the entity is called `TokenVotingProposal`, our script generates a class `ExtendedTokenVotingProposal` that extends from `TokenVotingProposal` and adds a couple of helper functions into this class. Most importantly, default values are included in such classes and are not scattered around, ensuring that they only live in their own respective places. Not only that, script also adds `assertEntity` to the class that loops through all the properties and checks whether the entity with those values are included in the database.

```js
export class ExtendedTokenVotingProposal extends TokenVotingProposal {
withDefaultValues(): ExtendedTokenVotingProposal {
this.id = PROPOSAL_ENTITY_ID;
this.dao = DAO_ADDRESS;
this.plugin = Address.fromHexString(CONTRACT_ADDRESS).toHexString();
this.pluginProposalId = BigInt.fromString(PLUGIN_PROPOSAL_ID);
this.creator = Address.fromHexString(ADDRESS_ONE);
this.open = true;
this.executed = false;
this.votingMode = VOTING_MODE;
/// some more properties
}

createEvent_ProposalCreated(
actions: ethereum.Tuple[],
description: string = STRING_DATA
): ProposalCreated {
let event = createNewProposalCreatedEvent(
this.pluginProposalId.toString(),
this.creator.toHexString(),
this.startDate.toString(),
this.endDate.toString(),
description,
actions,
this.allowFailureMap.toString(),
this.plugin
);
return event;
}

assertEntity(debug: boolean = false): void {
let entity = TokenVotingProposal.load(this.id);
if (!entity) throw new Error('Entity not found');
let entries = entity.entries;
for (let i = 0; i < entries.length; i++) {
let key = entries[i].key;

let value = this.get(key);

assert.fieldEquals('TokenVotingProposal', this.id, key, value.displayData());
}
}
}
```

There're a couple of things we achieved:

- The default values are not scattered anymore and only live in their appropire extended entity classes.
- There's no need to call `fieldEquals` multiple times for each field in the tests. Simply calling `assertEntity` is enough.
- Creating the events is much simpler, because it uses the same default values that live in the same class which brings much more safety.
- We can easily test to see if the saved entity in the database (fetched by the `id` field) matches the fields _currently_ on the Extended Entity. These will be the default values unless you overwrite them (see below for an example)

Usage of the extended class is the following:

```js
let proposal = new ExtendedTokenVotingProposal().withDefaultValues();
let event = proposal.createEvent_ProposalCreated(actions, STRING_DATA);
_handleProposalCreated(event, proposal.dao, STRING_DATA);
// If the event uses non-default values for a couple of fields
// Update the proposal's fields with those values
proposal.creationBlockNumber = BigInt.fromString(ONE);
proposal.votingMode = VOTING_MODES.get(parseInt(VOTING_MODE)) as string;
// assert
proposal.assertEntity();
```

**How it works behind the hood**

When you have a new entity in the schema, go to `/helpers/method-classes.ts` and add a new class that extends from that entity. Then add the functions that you want this extended class to have.

After running `yarn test`, our script('schema-extender.ts') loops through the classes in method-classes and cleverly generates the `helpers/extended-schema.ts` which can be used in the tests to use Extended classes instead of the original entity classes.

Setting properties on the extended entity (before saving) will allow you to run `assertEntity` to check that the properties _fetched from the database_ match the properties you've just set, else it will use the default values.

In the above example we are asserting that the `creationsBlockNumber` and `votingMode` are set to specific values, while the rest of the entity will be checked against the defaults.

## Github Labels / Workflows

### Workflows
Expand Down

0 comments on commit 6b3f5f8

Please sign in to comment.