Since our contracts are mainly interacted with through tests during the development process, it makes sense for us to adopt a test-driven process while coding.
That's why, in this chapter, we're going to iterate on our app by writing the tests first, and then implement the necessary features in our contracts to make them pass. Let's get started!
Retrieving the user info
The next step in our DApp is being able to retrieve a user's info (for now, that's only the ID and username) based on their ID. To do this in a test-driven manner, let's start by writing the test.
If you want to return structures in Solidity, you'll have to convert them to tuples, which in turn will be interpreted as arrays in the JavaScript environment.
Therefore, if our structure looks like this...
...we should expect to get a JavaScript array where the first element (of index 0
) represents the ID, and the second one (of index 1
) is the username.
This seems right, now let's write that getUserFromId
function!
Alright, let's try this out. If you run truffle test
, you'll see that our test unfortunately fails:

The reason the function returns "0x7472697374616e00000000..."
instead of "tristan"
is because it's hex-encoded. Whenever we retrieve a bytes32
value in a JavaScript environment, we have to convert it to a string before using it in our assertions!
Again, the web3
library has a utility function called toAscii
which we can use:

As you can see, the test still fails though because the returned string has a bunch of \u0000
characters at the end of it. Again, this is due to the fact that it's a bytes32 object and therefore must be exactly 32 characters long. \u000
is simply a representation of a "null" character.
In order to remove these trailing null characters, we can simply replace them through a simple JavaScript RegEx:

Using a public state variable
While our Solidity code above works great, there's actually a cleaner way to get all information for a profile.
By adding the keyword public
in front of our profiles
state variable, Solidity will automatically generate the getter function for us. In other words, we can skip the getUserFromId
function altogether!
This is a great time saver and also makes our code much more maintainable, since we don't have to return every single struct field in a separate function.
In our test file, we don't have to change anything, except that we call storage.profiles
instead of storage.getUserFromId
.
Run your test again, and you'll see that it works just as well as before. Nifty!

Creating the TweetStorage contract
Now that we're getting a hang of testing, let's write some more for our upcoming TweetStorage
contract!
Make a new folder called tweets
inside your contracts
folder, and add a file called TweetStorage.sol
in it:

As usual, we also need to add a line to our deployment file so that our test can actually interact with the contract:
Now that that's out of the way, the things that we are interested in testing are the following:
Creating a new tweet (and get its newly added ID)
Get a tweet's data based on its ID (for now, that info will be the tweet's ID, text, author ID and creation date).
As we saw with our UserStorage
, integration tests (in JavaScript) are a great way to verify the behaviour of our contracts from a user's perspective, whereas unit tests (in Solidity) are necessary to get the actual returned data from a writable function that performs a transaction.
When creating a tweet, we expect to be able to call a function named createTweet
in the TweetStorage
contract, pass a user ID and a text string as data, and get the newly created tweet's ID in return. In order to verify that tweet ID's value, we'll need to write a unit test.
Here's how we could write that test:
In order for it to pass, we're going to use some logic that's very similar to the one in the UserStorage
contract, using a struct, a mapping, and a state variable keeping track of the latest ID:
Note that we're using string
and not bytes32
to store our tweet text. As we know, bytes32
has a maximum limit of 32 characters, which isn't nearly enough for a tweet, so it's not very suitable for this use case.
We're also using the the built-in now
variable – which uses the current block's Unix timestamp – to set the time when the tweet was posted.
Getting the Tweet data
Now we can move on to the second test – getting the tweet's data! This one on the other hand, can be written in JavaScript. In fact, writing a unit test for this would result in an error since you cannot pass strings from one contract to another in Solidity.
Create a new file called tweets.js
in test/integration
and add the following code:
Notice how we're using the parseInt
function before we compare the tweetId
and userId
to a number. The reason for this is that web3 uses a special number type (called bigNumber), in order to support Ethereum's standard numeric data type (which is much larger than the one built into JavaScript). Since we're dealing with such small numbers in this test, it's okay to convert them like this.
This all looks good! However, if we run the test...
It seems like our tweet hasn't been created, since it returns an ID of 0
instead of 1
. But didn't we create the tweet earlier in our Solidity test?
Actually no. Our Solidity tests and JavaScript tests are completely separate! Just because we've created the tweet in our Solidity test, does not mean we can retrieve it in our JavaScript test – each test in Truffle uses a clean room environment so that they don't accidentally share state with each other (which is a good thing)!
The solution is that we'll simply have to recreate the tweet in our tweets.js
file as well. We can do this in a special before()
function, which runs before any other test:
You should now have an idea of how to combine unit tests and integrations tests in an efficient way to test different aspects of your contracts. You should also be aware of some of the gotchas with using types like bytes32
and uint
in a JavaScript environment, and how to convert them into something more suitable for your application frontend.
In the next chapter, we will start creating our controller contracts in order to add more advanced smart contract logic to our DApp.
Comments
You need a Ludu account in order to ask the instructor a question or post a comment.
in tweet.js
instead of this line of code:
const [tweetId, text, userId] = tweet
write this if get tweet is not iterable error:
const tweetId = tweet[0]
const text = tweet[1]
const userId = tweet[2]