The course is part of this learning path
In this lecture, we run through testing in Solidity and carry out a variety of tests on the game that we have been creating in previous courses in this learning path.
In the previous lecture, we edit our first little JavaScript test to test if the game board is empty. And now that we know how to write these tests, I would say we add a little bit more complexity and test winning and losing in JavaScript. And I'm going to add a new test here, basically just copy the file and paste it and my JS code will automatically paste it with a new index. But I'm not very happy with the file name. So, I just call it 2_test_winning_losing.js. I think it's just with 1.0. Don't blame me on my writing please. And we start with the same kind of test. We call it "ThreInARow Test Win", and let's call this test it("should be possible to win the game").
Now there are two things that we need to know here. The one is we don't need the board at this stage. The other one is the accounts array. How can we make sure that we are playing between two players? Because when we just call this startNewGame, then we didn't give it any account here. You can add the account from which this transaction gets started always in this optional last object here where we say our value is going to be 0.1 ether. You can also say I want to have this from: account [0] or from: account [1]. And these accounts array comes directly from our Ganache which is managing the accounts and is signing transactions.
So, we're going to need this in order to play between the two players, account [1] and account[0]. We started our game here already with 0.1 ether. And obviously we have to join our game now with the other player. So, let's do this; let and there's a lot of txReceptJoin is await.threeInARowInstance. There is no dot in between here, apologies for that, joinGame. And when we go to our, let me close this, threeInARow. Then we see joinGame, it doesn't have any arguments but it emits a number of events that we are going to check. So, we joined the game from our accounts[1] and the value is web3.utils.toWei( "0.1", "ether") as well.
Okay. Let's just console.log(txReceiptJoin). No, I made a mistake here. I start my ganache. If you've still running your ganache, then just keep it running and on a 2: powershell, I go to the right folder S04 and I truffle test in the test folder, the 2_test_winning_losing. So, it's not always starting all the tests. I hope this is still the right command. And we get our transaction receipt here and in the logs, we see that we have two logs, logIndex: 0, logIndex: 1 and we can make sure with an assertion now that we have a log PlayerJoined and we have the NextPlayer event here, a log here and inside we have the address of the NextPlayer.
So, we can determine who is going to be the first player. First one is assert.equal("NextPlayer") event should have been emitted as the txReceiptJoin.logs[1].event. And we also have to make sure that our txReceiptJoin.logs arguments should be the player that is the NextPlayer. And we can just basically use this in the from: field because this one just takes an address and that address will be sent off to ganache to sign the transaction. So, all we really have to do is await threeInARowInstance.setStone(0,0 from txReceiptJoin.logs[1].args) and then how is it called; _player. And what we're going to do next is we're going to see if our board = await threeInARowInstance.getBoard and then we are going to log this to see if it is logged correctly, console.log(board). Let's just start this and let's see what happens. We set our player which is one of the two players and it can change at any point because our player is pseudo-randomly selected by the block.number. If you remember back in our joinGame function, the next players are block.number and instead of finding out who is going to be the next player, we just take the address that is returned here and inserted into the from: field.
So, because we have access to both addresses, we're not playing one person against the other. We are simulating these two people. So, in our case, instead of finding out who, which one is the address for which account and using the account, we can directly use the address here. And then we setStone to 0,0. So, that might be our first player then we get the board back. So, all we have to do now is take the returned txReceipt here from, I give this a another name just to have a distinction here; txReceiptPlayed. And then we say the txReceiptPlayed which is then the NextPlayers event and this will be outputted over here.
If setStone is called, then it will emit at the very bottom of setStone. It will meet NextPlayer event with the next activePlayer which we can then use in turn again to play, to setStone for the NextPlayer. So, we have the player 2. Let me output this just to give you a better idea, console.log(txReceiptPlayed). And here we have the player. And this is going to setStone to 0. This one is setStone to 0,0. This is setStone to 0,1 and then we get the board again. Let's see. We have a lot of log output now. The first log output is this txReceiptPlayed which is this one here and inside here, we have one logs over here with logIndex: 0 which is the event NextPlayer.
And as an argument, there is a Result object. Let me output this argument here logs[0].args. So, you know what is going on. So, we have this log [0].args with one argument which is the player. So, we have the next player address which is either player one or accounts[0] or accounts[1]. And we can use that exact same trick again to send off a transaction from the right address and we get back again that the next txReceipt with the next player inside and then we can in alternating ways play the game. Let me just command this out.
All we have to do now is play the game until it's one. So, this is player one; player one again. Player one is setStone to 1,1. And then it's coming player two again; player two is setStone to 1,2. And then player one again. Player one is winning this game and here it gets interesting. If we console.log(txReceiptPlayed) here, then we should have also the winning txReceipt inside or the winning event inside our txReceipt. So, let's just have a quick look. So, we have now a different logs output. The one is a PayoutSuccess. And the second one, the second log is a GameOverWithWin; and this is what we're after.
We want to make sure we want to assert that our GameOverWithWin is done by the address or the argument is winning; that was the last player here. So, we can do this by simply going over here saying let winningPlayer = txReceiptPlayed and then we can make sure that assert.equal(winningPlayer, txReceiptPlayed.logs[1], the second log and then the args and how is this argument called. GameOverWithWin is _winner, "The winner is not the winner"; Let's give it a very creative error message in case this is not working. And we also make sure that our board on the diagonal which we set here 0,0, 1,1 and 2,2 is also going to have to winner address. So, we can make assert.equal(winningPlayer, board [0][0], "Left top is occupied by the winner"); [1][ 1], "Center is occupied by the winner". And then [2][2], "Bottom right is occupied by the winner". Let's just give this a try. Great, it's passing. What a surprise. Now in the second part of this lecture, it's up to you to write another test which is here for determining the, if somebody lost the game. So, in this case, you have to revise this logic a little bit. And if you want to do the coding challenge, then pause the video now. Or I'll also show you in a second how this is done. All right, then let's write the second test and you can add tests right here in the same file. Wrong key. Ctrl+ C, Ctrl+ V "should be possible to loose the game".
So, we are having the exact same information. Just that this time we are losing the game and we want to make sure that the winning player is the winning player. So, now to the challenge of this lecture that will be how can you test to draw the game. And I'm going to stop this video here and you can do challenge if you want to. And in the next lecture, I'm going to show you how you get the draw so nobody wins. There's the two sides are getting the money back. All right. I'll see you in the next lecture where we are talking about drawing.
Tom is a CTO, senior back-end developer, and systems architect with over twenty years of hands-on development experience in a variety of languages and systems. He has a CS master's degree and has been working with Ethereum and blockchain technologies since 2016.