Check out a free preview of the full A Tour of Web 3: Ethereum & Smart Contracts with Solidity course

The "Testing Storage Methods" Lesson is part of the full, A Tour of Web 3: Ethereum & Smart Contracts with Solidity course featured in this preview video. Here's what you'd learn in this lesson:

ThePrimeagen creates tests for a few player attributes. Since the attributes are randomly generated, the Hero contract is extended and the generateRandom method is overridden to return a value set by the test script.


Transcript from the "Testing Storage Methods" Lesson

>> So, hopefully, this all makes sense and you know what we should do? We should probably prove that we've written this correctly. Because I've written this thing about four times, now, and of those four times, I've gotten it right first try every time, but it usually was my first try the first time, one time.

So, let's go over to our nice little test file, and let's verify we've created a hero. Now, can anyone, off the top of your head, cuz you've all done software, you've all done testing, what is the hardest part about this testing? Can anyone tell me what part kind of really throws the testing into, just really makes it crap?

>> Random?
>> Boom, randoms. Awesome job, that was very perceptive. So, randoms is what suck in testing, right? Just totally the worst thing. So, let's get smart here. I'm gonna go up to random over here, and I'm gonna add in the word virtual. You're gonna probably say C++, I'm gonna say no, not C++.

Then I'm gonna create another file called TestHero. I'm gonna, of course, do my very special pragma statement. I'm gonna, of course, import Hero.sol. I'm then gonna create a contract called TestHero is Hero, right? Now it is this thing right here, right? And then I'm gonna go like this, public, well, actually, why do that when I can just jump here.

Copy this, and jump back here and I'm gonna add the word override, all right? And I'm gonna go return random, then we have function setRandom(uint r), boom boom, public random = r. Go up top, uint random, bam. We now have a test class in which we can set the random nature to our above class.

So, when you do the VRF stuff, you can do the exact same thing. You have your leaf class, your final class in your inheritance tree, contain all the code to interact with the random nature. But then you can create a test class below it that actually overrides those two random functions and provides an off chain testing solution.

So, that way, you don't have to try to get VRF launched into your local network, which would just be a nightmare. Or, here, when we're using kind of pseudo random, kind of our own little fake random, we can still do the exact same thing, except, for now with our tests, we can have predictable results.

This is beautiful and fantastic. It makes life much, much better. So, I'm very, very happy that Solidity does provide a way to override functions. And, in general, this is actually a pretty nice little test practice to be able to have a class that's separated out from your main class, and which adds in some extra testing features.

So, that way, your tests don't have to suck. Cuz or else, we're gonna have to just really it'd be very, very difficult. We could technically run the tests, find out what the random numbers are all generated, workout all the math, do that. But if we accidentally add a test in between, all those numbers change, right?

You'd actually have to have the order in which your tests run, be the order in which you get your random. It would just be awful. And then I also use timestamps, so, we'd have to make sure that the timestamp somehow is exactly what we want it to be.

It would [SOUND]. So, let's go back here. I have this, we've overridden it, so, now let's go to our test. I think that this last part will be the most interesting of it all. So, first, let's go in here, let's make sure we have enough money when we generate our hero.

So, there we go. We have enough money to generate our hero, and then we go like this, const h = this. And I'm also doing like this, const hero, I'm gonna shadow this thing out, await createHero. Just so I wanna make sure that there's no other information in here.

Plus, if this thing gets executed, the above test up here will just fail, right? Because all of a sudden, there's a hero there. So, we gotta make sure that we don't accidentally pollute any extra data. So, let's do this. We've created our hero and I just realized we don't assign it, that is naughty.

Now we go like this, const heroes = await hero.createHero, and let's plus in 0. I wanna be a mage. Mages are always the best at Diablo II. Ice mage, it was awesome better than a hammered in. Someone in [INAUDIBLE] would go, the robot that's all I ever did talk.

But we have our heroes and we just need the first one. Cuz the first one should be the one we just got done generating, correct? Now, before I go on, should I allow us for some time to get caught up? Do people need a moment? Do they need to see any of the code?

So, two good questions, or one really good question which is, if we use uint for stats, aren't we kind of way overallocating, right? Yes, we fundamentally are overallocating. But, a, it makes the shifting very easy. Now, if I were to write this myself, I would probably tighten up some of these items.

But I'd want to go and measure out the gas to make sure I'm actually doing the right thing, cuz you still have to cast certain things. So, this stat, being a uint8, which we could make it a uint8, I'd also have to go down here and make this and make sure that it's actually a uint.

Or we wouldn't want that to be a uint, we'd actually want our value to make sure it at least has to be a uint32. And we could technically make this value like a uint8, right? We could go in here and really kinda tighten up all the bounds, but you really wanna make sure you don't make any mistakes.

And I don't know if it's any cheaper gas-wise, using more or less physical, the memory side of things or the stack level memory in a function. I don't know how much that affects gas. I haven't done enough research to really give you a solid answer on that one.

I do know that when it comes to slot memory or storage memory, it is technically easier using 256 bits than it is using 8. There'll be some good reasons and I'll show you why. So, we'll see. But, you use more memory. So, does it cost more in the long-run, or does it just cost more in the short-term for the initial storage?

We'll talk about all those things. All right, so, let's go back to testing. So, now, what I need to do, is I need to determine what happens with our algorithm? So, what I'm gonna do is, I'm gonna go like this, await hero.setRandom(69). Yes, we're still following Lord Elan's requirements of always meaning at all times.

And what I need is, I need to change hero, not as a hero, but as a TestHero, right? It's the one that contains that extra function, setRandom. That way, I can ensure that we produce the same hero every single time. I think that that part is very, very important.

And so, there we go. So, now, we've guaranteed setRandom will be our code number and heroes will produce the same hero every single time. So, now, let's do this, expect(await hero.getStrength, and then we can pass in, let's call this thing h. Honestly, I just don't wanna have to type it a bunch.

There we go, pass in h to.equal, and then that's what we gotta figure out. Is we need to go through each one of these and figure out, what are we going to get? Cuz if we've done it correctly, we should be able to work out these answers. So, let's do this.

I'm gonna go, let's go echo, and I wanna take, first, we need to select it out. So, all right, hold on, let me just jump back here to make it easier for you. So, the first thing we need to do is generate a position. So let's go echo 69 modulo 5.

So, the first stat we're selecting out is 4, right? It's the fourth item, which happens to be magic. So, all right, so let's jump back here and I'm gonna go here and go, magic, all right? Let's just make sure we get this thing correctly. We're gonna jump back here, and then I need to echo out our value, which we're gonna do a second 69 modulo 18, right?

Then, I gotta add 1. So, 16 should be our value. If I've done this correctly, 16 should be our value. So, I'm gonna go in here and make it 16. And let's just run this thing to make sure, did I get magic correct? First try, have I added any sort of broken stuff up?

I forgot to do one very important part. When you create an array, you have to say where is it stored. It's stored in the function. Memory represents stack memory is how I say it. I don't know if that's technically where it's actually stored. Is it actually stored in a stack or is it stored on the heap as they run their theorem virtual machine?

That's not an answer, we have to do it, we can just think of it like it's stack memory. A little bit easier to abstract certain concepts. So, let's go and let's run our test one more time, and there we go. So, now, let's see, what do we have?

Invalid big number value, okay. Well, we've kinda screwed some things up. So, let's go in here and let's not return uints, right? We can make this a little bit smaller so we don't get these big values, right? We already know that a strength will be between the values of 0 or 18, we don't need to send back 256 bits of data, right?

So, I'm gonna just simply take uint and replace it with, let's just do uint32. Does it really matter? Probably not. So, go, no, yes, no, yes, no, yes, no, yes, no, yes. There we go. Quick little thing right there. There we go. We pass in the uint hero.

We pass back a u32. Which means I do need to go like this. I'm gonna highlight all of these, do a little bit of fancy stuff here. Let's do a Yeah, yeah, do a fight and one-eyed Kirby. You'd know what that is. If you're doing my class, you would know this by now.

And then what I'm gonna do is, I'm gonna go uint32(hero that, boom. Yes, this is beautiful. Look at that. There we go. So, I'm gonna cast the whole thing into a uint32. And let's try it again. So, I'm gonna go back here and execute this. This should avoid us having to get a big number.

And it's gonna say, hey, you've done something wrong. And I technically do agree with it, I have done something wrong. My find and replace was actually not that awesome. I thought it was awesome, but it turns out it wasn't that awesome. I forgot a parenthesis, there we go [LAUGH].

We're back in business here. I've put the uint, I've cast everything to a uint32. So now I should be able to send it, and I should be able to get the result we expect. It's taking a moment. There we go. Again, we're getting yet another big number. Let's find out what's happening here, cuz maybe this isn't what I expected it to be.

So where are we, where are we? An invalid big number as argument, let's see. Invalid big number value as argument. All right, so, what do we get here? So, am I doing this thing correctly? So, first, let's see, createHero, should go out and get us a hero, right?

My goodness, yes, I'm so silly, people, I'm so sorry, look at this. I did create hero and then assign that. We know you don't get a value out of a method you do an operation on, I have to then go h = await hero.getHeroes. Remember, we gotta go get my list of heroes, and then I'm gonna grab the first one out.

This should properly give the answer. So what was actually, effectively, happening here was, it was saying that the big number we were passing was interact. We were passing a value of undefined. Of course, that makes perfect sense, right? I was giving you a value of nothing. All right, okay, yeah, that's a little bit silly of me.

But it happens when you're in the weeds. There we go. So, there we go, we have a new one. Please send more money. Well, again, I'm a fool, I forgot to send money. Wow, I'm creating multiple heroes. There we go, that's my problem. Look at that. I created multiple heroes, fantastic.

Let's do this. So, it turns out you can distract me with a little bit of talking. But we got there. Awesome, we have a new invalid number. I've done it, somehow, again. So, let's find out what's happening. Let's print out our nice little console.log(h). We should be getting back out our heroes.

Perhaps, I've wrongly specified this, perhaps we're not saving it to the chain. So, let's find out what's happening here. It's very easy to goof these little things up. Here we go, let's print it out. What do we see here? Well, we see that I definitely am getting a big number right here.

So, I'm a little bit surprised that we're not seeing that value. So, let's go back here and I should be selecting out the 0th item, which should be a number that is enough for them to handle. My [LAUGH]. And so the problem was, since I did this, what happens?

Order of operations with JavaScript, this thing returned a promise, I got the 0th index of a promise, then I awaited it. Of course, that makes no sense, right? [LAUGH] Why would you do that? Good old fashioned order of operations, right? All right, so here. Fantastic, beautiful, right? We now got that, there we go.

Sorry about that, now we have this. We now have the magic, we're ready to rock. So, now, we can do this again, but we need to figure out the next stat. So, what happens right here? Well, length becomes 4, correct? But more so, something else happens. We swap out magic with whatever's at the end of the array.

Well, what's at the end of the array? Magic, so, does it really matter? No, cuz we're ignoring that number. So, we kinda got away with our first one pretty easy. So, let's generate the next one. Let's go 69 modulo 4, which is 1. So, this time, we're selecting out this stat.

And then, of course, we're gonna be doing 69 modulo 17, this time, which is 1 add 1, 2. So, we should have health of 2. That is what I am saying, FM health, we should see health of 2. Let's also draw our array. Cuz, remember, we had strength, health, dexterity, intellect, magic.

And then, the next round, we set magic to be magic. And then we set our length to be 1 less. And then we selected out h. So, I should replace H. And then, really, we're ignoring the last element. So, this is what our third round should look like.

But let's verify that we've done things correctly, is health 2. I wanted to kinda work through this so you can kinda see how this works. Let's see, I just closed down my poor little node. Dang it, so, I don't wanna have to source my privates again. So, there we go.

It goes through and validates that everything works. There we go, there we go. So, we're back up. I don't think I need to keep going through this. I think everyone sees exactly where I'm going with the remainder of this. But I just simply have to work through my problem, figure out every single value.

And I've created a test that reliably creates the same hero, despite the actual program creating random heroes.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now