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.
Therefore, if our structure looks like this...
0) represents the ID, and the second one (of index
1) is the username.
This seems right, now let's write that
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
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.
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
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
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
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
Create a new file called
test/integration and add the following code:
Notice how we're using the
parseInt function before we compare the
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?
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
In the next chapter, we will start creating our controller contracts in order to add more advanced smart contract logic to our DApp.