4.9 KiB
Contract tests
Now that we have our contract written, we can use the code generation provided by EmbarkJS
to write our contract unit test cases for test-driven development. Embark will code generate a DTwitter
javascript object for us from our contract and make it accessible in our dApp by way of the DTwitter
object.
Additionally,
EmbarkJS
allows us to interact with decentralised storage (IPFS and Swarm) and decentralised communication (Whisper), which are all configurable in config files.
Create account transaction should be successful
Our first test is to ensure that our createAccount
transaction is sent successfully. Let's fill in the code that calls our createAccount
contract function:
// do the create account tx
const createAccountTx = await DTwitter.methods.createAccount(username, description).send();
Notice the
.send()
at the end. This is required when we are sending a transaction that will modify the state of contract. In this case, we are modifying the state by adding a user to theusers
mapping.
Should create user
After our successful createAccount
transaction, this should have created a user. In our test, let's fill in the code to retrieve a user from the contract.
// Get user details from contract
const user = await DTwitter.methods.users(web3.utils.keccak256(username)).call();
This time we used a
.call()
. Why? Because in this case we are not modifying the state of the contract, and simplying querying data from it. Changing state costs gas, so we use.send()
, while querying state is free, so we use.call()
.
Should create owner for default account
The createAccount
function should have also created an entry in the owners
mapping with our defaultAccount
, which we check in the next test. This is how we retrieve an owner from the owners
mapping:
// read from the owners mapping the value associated with the defaultAccount
const usernameHash = await DTwitter.methods.owners(web3.eth.defaultAccount).call();
User exists should be true
We can use our userExists
function in the contract to check if a usernameHash
exists. This will later be used for username validation in the dApp. Our test for userExists
can be completed using:
// Check the usernamehash exists
const exists = await DTwitter.methods.userExists(usernameHash).call();
Edit Account should update user details
After a call to editAccount
, our user's details should be updated. This unit test can be completed by adding the following:
- Call edit account
await DTwitter.methods.editAccount(usernameHash, updatedDescription, updatedImageHash).send();
- Then fetch the user details with the usernamehash
const updatedUserDetails = await DTwitter.methods.users(usernameHash).call();
Tweet event should fire when there is a tweet
We need to ensure that our contract events subscription works correctly when someone creates a new tweet via the tweet
function. In our unit test, we can add the following:
- Subscribe to the
NewTweet
eventDTwitter.events.NewTweet({ filter: { _from: usernameHash }, fromBlock: 0 }) .on('data', (event) => { assert.equal(event.returnValues.tweet, tweetContent); });
- Do the tweet
await DTwitter.methods.tweet(tweetContent).send();
Run tests
Let's run the tests to ensure they are all passing. Type quit
in the Embark consle, then run:
run_embark test
Test results
Your test results should appear similar to the following:
DTwitter contract
✓ transaction to create a dtwitter user 'testhandle' with description 'test description' should be successful (114ms)
✓ should have created a user 'testhandle' (60ms)
✓ should have created an owner for our defaultAccount
✓ should know 'testhandle' exists (49ms)
✓ should be able to edit 'testhandle' user details (186ms)
✓ should be able to add a tweet as 'testhandle' and receive it via contract event (54ms)
Note: there is a known error with
ganache
that sometimes causes last test does to fail with the errorERROR: The returned value is not a convertible string
. We are currently investigating this issue.