There are 3 ways to mock data to develop on Pioneer
In stories data are mocked using the MockProvidersDecorator. This Decorator expects either an object or a function returning an object in the story parameter. This object has the following structure:
{
accounts: {
active: account and membership data or handle defined in the list property
list: list of account and membership data
hasWallet: boolean
}
chain: {
consts: This mirrors the Joystream API consts structure. The contants can simply be defined with JS primitives (no need to create types).
query: This mirrors the Joystream API query structure. This too can be defined with simple JS primitive no need to defined functions returning RXjs subscription returning Polkadot types (although this is accepted too for edge cases).
derive: Same as query.
rpc: Same as query.
tx: {
[module]: {
[extrinsic]: {
data: The data returned on success
failure: If this is defined and a non empty string the mocked transaction fails with this message
event: The name of the chain event triggered by this transaction
fee: The fee of the transaction
onCall: This is called whenever the tx function is called (so whenever the transaction is updated)
onSend: This is called when the transaction is sent
}
}
}
}
gql: {
queries: [
{
query: DocumentNode,
data: The data fields returned when the query succeeds
error: If this is defined the query will fail witht this value as it's error
resolver: The resolver to run for the query if defined it will ovewrite both data and error
}
]
mutations: [
{
mutation: DocumentNode
data: The data fields returned when the mutation succeeds
error: If this is defined the mutation will fail witht this value as it's error
resolver: The resolver to run for the mutation if defined it will ovewrite both data and error
onSend: This is called when the mutation is sent
}
]
}
localStorage: { [property name]: string value }
backend: {
notificationsSettingsMap?: BackendContextValue['notificationsSettingsMap']
onSetMemberSettings?: (memberId: string, settings: any) => void
authToken: string
}
}
Note
Most properties of this object are optionals. Only what's needed to render the stories and run the tests should be mocked.
For more details check existing stories like: ProposalPreview.stories.tsx.
To test most of the extrinsics requires existing on-chain data. To create some on-chain objects use the yarn run node-mocks
script or use the polkadot apps wallet application to create them beforehand.
Available commands:
yarn workspace @joystream/pioneer node-mocks council:elect [-d BLOCK_TIME¹] [--to ELECTION_STAGE]
- Run an election until the specified stage: VOTE, REVEAL, or IDLE (default)yarn workspace @joystream/pioneer node-mocks council:announce
- Announce enough candidacies to start the voting stage when the announcing stage endsyarn workspace @joystream/pioneer node-mocks council:vote
- Vote for the announced by the previous command candidate to start the revealing stage nextyarn workspace @joystream/pioneer node-mocks council:reveal
- Reveal the votes casted by the previous command to start elect a new council and start the idle stage nextyarn workspace @joystream/pioneer node-mocks members:create
- generate memberships using query-node mocks datayarn workspace @joystream/pioneer node-mocks set-budget
- Set membership Working Group budgetyarn workspace @joystream/pioneer node-mocks opening:create [-d BLOCK_TIME¹]
- Create an openingyarn workspace @joystream/pioneer node-mocks opening:fill
- Fill existing openingyarn workspace @joystream/pioneer node-mocks upcoming-opening:create
- Create an upcoming openingyarn workspace @joystream/pioneer node-mocks forumCategory:create
- Create a forum categoryyarn workspace @joystream/pioneer node-mocks transfer
- Transfer tokens between accounts
(¹) BLOCK_TIME
is the time between each block. It is 6000ms by default but on testing chain it is 1000ms. Therefore when running some of the scripts on these testing chain -d 1000
should be added for the command to succeed.
To show help:
yarn node-mocks --help
Another way to influence the on-chain state for testing purpose, is to provide a customize chain-spec.json
file when running a Joystream node:
-
Create
packages/ui/dev/chain-spec/data/chain-spec.json
if it does not exist:- Either with docker compose:
docker-compose run --rm build
(the first time thechain-spec.json
file is generated this way, the file ownership might have to be fixed). - Or directly with the runtime binary:
<path to the runtime> build-spec --dev > packages/ui/dev/chain-spec/data/chain-spec.json
- Either with docker compose:
-
(optional) Change the starting Council/Referendum stage (the default is
Announcing
):- Run
yarn workspace @joystream/pioneer helpers setChainSpec -s <stage> [-d <duration>]
- The
stage
parameter (required) can be eitheridle
,announcing
,voting
, orrevealing
(these should be lowercase) - The
duration
parameter (optional) set the number of blocks this stage will last.
- The
- Run
-
Start the node:
- Either with docker compose:
docker-compose up node
- Or directly with the runtime binary:
<path to the runtime> --tmp --alice --validator --unsafe-ws-external --unsafe-rpc-external --rpc-cors=all --chain packages/ui/dev/chain-spec/data/chain-spec.json --log runtime
- Either with docker compose:
> yarn workspace @joystream/pioneer run helpers decode -t [TYPE] -v [VALUE]
This command requires two arguments:
-t
,--type
: The expected type of the value to decode. It can betext
, or the name or alias of a metadata class.-v
,--value
: The hash or the string representation of theUint8Array
to decode.
With -t text
this command will simply decode encoded plaint text values. E.g:
> yarn workspace @joystream/pioneer run helpers decode -t text -v 0x4c6f72656d20697370756d
Lorem ispum
Otherwhise the type options should refer to a metadata class. It can be the name of the class:
> yarn workspace @joystream/pioneer run helpers decode -t CouncilCandidacyNoteMetadata -v 0x0a0a616...
CouncilCandidacyNoteMetadata {
...
}
Or it can be an alias:
> yarn workspace @joystream/pioneer run helpers decode -t candidacy -v 0x0a0a616...
CouncilCandidacyNoteMetadata {
...
}
The available aliases are: post
, opening
, thread
, bounty
, candidacy
, candidate
, application
, member
, and membership
.
yarn workspace @joystream/pioneer run helpers commitment -s <salt> [-a <accountId>] [-o <optionId>] [-c <cycleId>]
- Calculate a commitmentyarn workspace @joystream/pioneer run helpers nextCouncilStage
- Wait until the next council stage start
You can also connect to the node using Polkadot apps wallet to interact with the node.
Warning
These mocks are now deprecated they are still heavily used by the legacy tests suites and some old stories. However no new tests or stories should depend on MirageJS and the legacy tests and stories should be removed progressively.
To mock the query-node server we use Mirage JS in tests, storybook data and for local development.
All MirageJS & query-node mocks are stored inside the @/mocks
.
In order to properly mock an Entity
you should:
- Prepare mocked data
- Write a generator that re-creates seed raw data as JSON file.
- See generators for examples.
- Write a MirageJS seed function
- A seed function will create proper MirageJS database entries from raw data.
- It should add only the data used by queries. Other information can be omitted.
- A seed function may create related objects and/or add additional mock information.
- Optionally: Some relations doesn't translate well in MirageJS GraphQL implementation. See
fixAssiociations()
for details.
- Run the
yarn query-node-mocks
to recreate mocks - Add GraphQL query resolvers
- Resolvers are used to handle the passed GraphQL query and return the data in a similar fashion to Hydra's GraphQL server.
- In most cases you'd only need to add a general query resolver for each type of queries:
getWhereResolver('Entity')
- returns a resolver that handles multiple results are returned (many), also used for paginated results, e.g.forumPosts
,memberships
getUniqueResolver('Entity')
– returns a resolver that handles unique results (one), e.g.forumPostByUniqueInput
,membershipByUniqueInput
getConnectionResolver('Entity')
- return a resolver for relay-style pagination results, e.g.forumPostsConneciton
,membershipsConnection
-
"Mirage: The xxx model has multiple possible inverse associations for xxx.xxx association"
See
fixAssociations()
for similar errors and fix. -
No data fetched from the query
See if proper query resolver is present in the
@/mocks/server.ts
file. -
No associated data in the mocked response
This might be a case when seeding, instead of passing a MirageJS object a simple object was passed.
// Wrong: server.schema.create('Parent', { name: 'foo', child: { name: 'baz' } }) // Correct: server.schema.create('Parent', { name: 'foo', child: server.schema.create('Child', { name: 'baz' }) }) // Also OK if Child's 'id' is known: server.schema.create('Parent', { name: 'foo', childId: '7' })
Seeding MirageJS can be slow, so when writing tests or stories, it's important to only add the truly essential data to MirageJS. For instance seeding all mocked members data with in a test suit or a story should be avoided.seedMembers(server)
One way to seed fewer data is to manually pass them to the singular seedEntity()
functions. For example with ElectedCouncil
:
seedElectedCouncil({ id: '0', electedAtBlock: 1, endedAtBlock: 2 }, server)
seedElectedCouncil({ id: '1', electedAtBlock: 3, endedAtBlock: 4 }, server)
A drawback of this method is that: the code needs to be updated whenever the entity fields change. Like if electedAtBlock
is rename to electedAt
, or if a new field is added to ElectedCouncil
. So it's important to keep most of this data in one place, like in packages/ui/test/_mocks/server/seeds/index.ts.
To avoid this issue, some plural seedEntities()
truncate the raw data. For example:
seedMembers(server, 2)
Here only the first two members from the mocked @/mocks/data/raw/members.json file were added to the MirageJS database.
Finally in order to both reuse and customize mocked data. Some plural seedEntities()
can be implemented as follow:
seedCouncilCandidates(server, [{ memberId: '0' }, { memberId: '1' }])
Here the first two candidates from @/mocks/data/raw/candidates.json
were added to the MirageJS database, but their memberId
s where changed to match the two members previously seeded.