As you might know, I’ve been spending the last few months or so teaching a friend of mine how to code. Lately, I started practicing TDD myself and decided to introduce this technique to him too.
As a first project written with TDD we decided to go with a simple pong game. It’ll have two paddles and a ball, the ball should move in a random direction, and change its direction once it hits something. Simple enough.
The first class he started writing was the
Ball class. He wrote a test that checks whether the ball has an
y position, then he wrote some code to pass that test. He moved on to testing that the ball can move and implemented a
move() function that changes the position of the ball by a constant value and the test passed. After writing the
move() function, he thought to himself (yes, I can read minds) – “wait, this shouldn’t be a constant value, it should be a randomly generated one”, so he decided to add a function that randomly generates these values. While he was writing the code that does it, I looked over his shoulder and saw that all of his tests are passing. According to the third law of TDD by Uncle Bob – “You are not allowed to write any more production code than is sufficient to pass the one failing unit test”, so he shouldn’t have written that function. I stopped him, and we started to think about what kind of test he should write that would test the change he wanted to make in the code. The first and most trivial solution was to write a test that creates two balls, moves them and makes sure that they move in different directions, but according to testing best practices, a test should be deterministic. It shouldn’t fail randomly even if the odds are very low for it to happen. So, this test wasn’t the test we were looking for. After some more thinking, I remembered another rule of testing, that a unit test should only test the class it was intended to test, and not any other code, but if we were going to test that the ball generates the direction randomly, we were also going to test the
Math.random() function, and we shouldn’t.
Now everything was clear, we had to mock
Math.random(), luckily we found an npm module that does exactly that – jest-mock-random. All we had to do, was to mock the
random() function and make it return predefined values, then make sure that the ball moves at the expected direction. 100% deterministic test.
It was so fun and enlightening for me to be able to understand that something is wrong with a test without understanding exactly what is wrong with it or how to fix it just by knowing that it doesn’t comply with the rules and then using the rules and best practices to craft the perfect test that does exactly what it should.