Not to take credit away from any author who’ve written that type of post, because I myself do believe that it covers the fundamental idea behind testing, but it still doesn’t make up for the fact that it’s highly unrelated to much of the code that we write.
Those type of problems are almost always calculated synchronously and include no external dependencies.
So it can be hard for beginners to take what they’ve read and map that over to asynchronous functionality that sometimes rely on up to three outside API’s.
Defining the problem!For the purposes of the blog, we’re going to write a user class which does two specific things, register a user and delete a user.
I will be using a simple node environment set up withNode-postgres (database solution)Jest (Facebook’s testing framework)Sinon (Mocking library)Hey so before we finally get started, take a look at this picture of a cat I found online! Not really a reason as to why, but it seems trendy here on mediums code related blogs so I thought I’d throw one in myself.
:DPhoto by Taylor Grote on UnsplashTime to TestTo begin, first lets create our test script in our package.
json fileNow we can begin setting up our files.
I’m going to start here by setting up our database file that were eventually going to be stubbing out in our tests.
This file here just exports out the pool connection option from the node-postgres library.
With that out of the way, we can now set up the user class.
Testing When There is A Definite Return ValueNow we can start implementing the business logic.
So as we discussed above, we are going to be focusing on how to properly unit test asynchronous code.
So as with any user class definition, arguably most important function of that class would be to register that user and save him/her to the database, so we’ll start here with our implementation.
To quickly sum up this snippet of code, we run a query against the provided user data to insert a record to the database and return a copy of the user model.
Now up to this point everything should seem pretty straight forward and standard, but to many beginners, running a test against this piece of code can be quite intimidating.
Some common questions will be:What am I testing for?Do I run the query in my test?Do I stub the database call?If I’m stubbing the database call by providing its return value, how do I know my logic actually works!?All of these are essential questions to master when writing tests and writing tests more confidently.
So lets see how this all works out.
A quick assumption for a case like this is to test for the correct return data.
Testing for the correct return data will ensure that this code works properly and any client code that depends on this function will also work as expected.
Testing that the returned createdUser object has property x then asserting that createdUser.
x is equal to a particular value is sort of a redundant, but with that being said, I have encountered code where devs prefer to do it this way.
In my book since they both essentially do the same thing, I would choose brevity and test that the values are equal to what I expect.
This code snippet above is the bread and butter of what unit testing is.
Following the three A’s of unit testing arrange, act, assert we were able to setup our user, stub out any dependencies, run the unit under test, and make assertions on our expected results.
One important thing to consider before we continue on is how earlier on in this reading we made testing our code possible through injecting our database dependency through our class constructor.
This turns our database into a member variable which is important because we can now grab a reference to it within our tests an stub it out.
Remember “new” is glue and you’re class has no reference to any newed up variable unless it was provided through the constructor.
Since we now understand how our code was made testable through dependency inversion, scroll back up and take a look at how we created our stub.
Not gonna dive to deep into sinonjs for this blog but using some sort of stubbing functionality is essential when dealing with dependencies.
Since in our user class, the register function returns a promise, we are forcing the stub to resolve for this case.
And how do we know what the function will resolve? Well for one we defined the registration function to return a copy of the user model upon a successful call.
So that’s a great place to start.
But what about the outer object with the rows property? Well that just comes from having an understanding of how the outside API that you are using returns its data.
I know through reading the docs that node-postgres returns an object with a rows property which is an array of objects, along with a rowCount property among many others.
But in this case my source code only depends on the rows property so that’s all we need to worry about in our test.
Why clutter it up with unneeded information?With this knowledge in place, we can go ahead and force our stub to resolve the expected data that it would under normal circumstances.
And if you were able to put two and two together, you can see that we just wrote our first scenario test! Not bad huh? Lets go ahead an run our first test to see it at work.
At the end of this blog there will be a couple other important things that we can add to this test that may prove to be helpful later on.
Testing a function with a void return valueLet’s move on to testing a function which returns void.
We’ll start by first defining what the function does.
Simple enough, the function runs a query to delete the user with the specified user id.
So question: How do we test this function? It’s obviously not returning a value, it’s not changing the value of an internal member variable that we can assert against.
So do we just make sure the function runs and doesn’t throw an error?Although that suggestion was on the right track by mentioning that we do some error checking, just making sure the function runs, though, is not the essence of what this function is supposed to be doing.
With that said, lets just jump into another test.
If you read the description of the test case, then yes, making sure the function made a call to the required dependency, is in this case the best we can do.
In fact, it’s pretty much the most we can do.
Remember, this is not an integration test, we aren’t testing that these two things work together, we just want to make sure that our function at least calls the outside dependency that it rely’s on right? If it didn’t do that then our code could never work.
Lets see our results.
In the paragraph above I was talking in absolutes saying, “it’s pretty much the most we can do.
” But there are other important cases to test within that scenario, but for the purposes of capturing the essence of what it does, Ill stop there and leave the rest to you :)Preforming a Negative Test Case ScenarioOne thing that I found myself doing as a beginner to unit testing and one thing that I find common among many new devs is the failure to test for negatives.
In other words testing that an error should be thrown when an error should be thrown.
Testing for negatives in your code base is just as important as testing for positives because error handling is key in any application, big or small!Now some more code!Very quickly, we included a new method registrationValidation and at the top of our register method we run an if statement on that validation method.
If it fails we want an error to be thrown.
Because the register function now rely’s on another method, it is essential that we go through our other tests relating to the register method and create the stub there to ensure that we are not unknowingly writing integration tests.
Here we are stubbing registrationValidation method and forcing it to return false.
(Note how we used returns and not resolves because this method does not return a promise) And because we expect this scenario to throw an error, in the catch block we assert that e is not equal to null.
That’s it!As a side note, depending on how you handle your error messages it may be more helpful to write an assert against the actual meaning of the message, but this should get the point across.
Testing for negatives in your code base is just as important as testing for positives because error handling is key in any application, big or small!A Complex Example — Unit Testing a Method with Multiple External API CallsTo end this blog, I want to leave you with a more complex example made easy.
Testing code that has more than one dependency.
The idea I have is to insert the time stamp of when the user registers into the database.
Now when we insert the user, we can grab the current timestamp, insert it, then return it with our user model for the client code to use.
To keep it short, we created a new test to ensure that user.
toLocaleString gets called, because this is important for our code to function.
Then we updated our old tests to include a stub for this new dateTime member variable.
Can you think of other ways we can provide tests for this new integration?So in the subhead I used the phrase “A Complex Example”.
And indeed, this is definitely more complex than what register method did previously, but did it seem any more complex than the things we’ve already done?.No not really!Lets see our results :DTo conclude, unit testing is easy!.Decide on the scenarios that you feel are important to the functionality that you are building, inject any external dependencies, and write a test case!.Remember the three A’s of unit testing; arrange, act, assert or as I like to call it, arrange, stub, act, assert and you will be well on you’re way to becoming an expert unit tester!Hope this was helpful and hope you enjoyed!.Cheers to better written applications!And oh yeah, Here’s a link to the code repository over on Github.
Please clone or fork it and play around.
And if you find ways for me to improve please make a pull request.
rayjohnson1/unit-testContribute to rayjohnson1/unit-test development by creating an account on GitHub.
comExtrasSo as I promised earlier, we will learn a neat trick that we can do with sinon (and other mocking frameworks in this matter) to spy on a stub and test that it was called with a set of specific parameters.
Once again we wont dig to deep into sinon itself, but I feel that this can prove to be a beneficial aspect to unit testing so lets quickly go over it.
Back to our registration method, lets make sure the query stub is called with all the values that will be inserted into the database.
Why?.Because I feel that this is important and if refactoring in the future causes me to miss one of the values, well that would be a bug.
Lets give it a go.
Here we went back up to the first test that we wrote for the registration example and added another assertion.
We are doing exactly what we said, we are just making sure that the query is called with the values we expect it to be called with.
Note how for the actual query text we just match it to any string.
The reason I choose to do it this way is because there are multiple ways to write a query and get the same result and if I tested a particularly syntax, and changed it later on, even though my result will be the same, the test will fail and this my friend is what I like to call a brittle test.
Thanks for reading!.