Coding

JavaScript Testing – Mocking Async Code

  • 00:00:01 hi welcome to this video I already got a
  • 00:00:04 video where I dive into JavaScript
  • 00:00:07 testing and I explain what it is why we
  • 00:00:10 would do it and how it generally works
  • 00:00:12 with the jest j/s package in this video
  • 00:00:16 I'll build upon that last video so
  • 00:00:18 definitely check that out and the
  • 00:00:20 article which belongs to the other video
  • 00:00:22 and in this video I'm gonna dive into
  • 00:00:24 how we can test asynchronous code
  • 00:00:27 specifically how we can test
  • 00:00:29 asynchronous dependencies like for
  • 00:00:32 example functions we got where we let's
  • 00:00:34 say execute some code where we reach out
  • 00:00:37 to an API how can we test such code
  • 00:00:40 efficiently with just Jas let's take a
  • 00:00:43 closer look
  • 00:00:47 I again prepared a simple project for
  • 00:00:50 this video now you find it in a link
  • 00:00:52 below the video in the video description
  • 00:00:53 and if you start the project by double
  • 00:00:57 clicking the index.html file you'll see
  • 00:00:59 a button there if you click that button
  • 00:01:01 you will eventually see an all uppercase
  • 00:01:04 text being printed here now responsible
  • 00:01:06 for that output is this code here I got
  • 00:01:09 a very simple setup here using webpack
  • 00:01:11 and here I'm getting access to a button
  • 00:01:14 in my Dom I'm adding a listener to print
  • 00:01:16 a title in print title I call a load
  • 00:01:19 title which is this function here in
  • 00:01:21 that function I reach out to fetch data
  • 00:01:23 which is provided in a different file
  • 00:01:25 also created by me there I seem to get
  • 00:01:28 some data object of which I extract the
  • 00:01:31 title I then transform this title to be
  • 00:01:34 all uppercase and I return it hence I
  • 00:01:36 can print it here now if we have a look
  • 00:01:38 in this HTTP file into this HTTP JS file
  • 00:01:41 here I'm using a third-party package
  • 00:01:44 Axios so this is not written by me this
  • 00:01:46 is an our third-party package and here
  • 00:01:48 in fetch data I make a get request to a
  • 00:01:51 dummy API so this is a real API in the
  • 00:01:53 web a real request is made here this API
  • 00:01:56 is not maintained by me it's just an API
  • 00:01:59 we can use for playing around for
  • 00:02:00 testing and there I get back a response
  • 00:02:03 object
  • 00:02:04 Axios gives me such a response object
  • 00:02:06 which happens to have a couple of
  • 00:02:08 properties like the status code for
  • 00:02:10 example but also the data property which
  • 00:02:13 I'm extracting here this is essentially
  • 00:02:15 what I do now let's write some tests I
  • 00:02:18 already installed the just package here
  • 00:02:23 and I also already configured my test
  • 00:02:26 command here to use gest I also have a
  • 00:02:29 project where I use node.js
  • 00:02:31 import-export syntax because just uses
  • 00:02:34 that by default and just as with my
  • 00:02:36 other testing video where I gave you an
  • 00:02:38 introduction to testing with just there
  • 00:02:41 I explained that just use the stats and
  • 00:02:43 tags that's setting it up to understand
  • 00:02:45 a different syntek would be more complex
  • 00:02:47 and I still don't want to do that here
  • 00:02:49 because here I want to focus on testing
  • 00:02:51 async code so we got everything set up
  • 00:02:54 to write some tests let's do that you
  • 00:02:57 learned in that last video that you can
  • 00:02:59 create a new file
  • 00:03:01 with dot test dot J s at the end to have
  • 00:03:04 just J's pick it up and executed
  • 00:03:06 automatically so this will be the
  • 00:03:08 testing fall for app jeaious and let's
  • 00:03:11 say we want to test print title
  • 00:03:13 conveniently I'm already exporting it
  • 00:03:15 here at the bottom so what can we do
  • 00:03:17 well in app test j s we can use the test
  • 00:03:21 function which is globally available
  • 00:03:22 when we run that file with just there we
  • 00:03:26 give it a description some text yeah
  • 00:03:29 some description of what should be
  • 00:03:30 tested here and there I have should
  • 00:03:35 print an upper case text for example I
  • 00:03:38 have my arrow function then that should
  • 00:03:41 execute or that contains the testing
  • 00:03:43 logic and in there we define our
  • 00:03:45 expectations now we could expect
  • 00:03:48 uppercase text with the help of some
  • 00:03:51 regex magic or anything like that but
  • 00:03:53 here because we'll change that anyways I
  • 00:03:55 just want to expect this text because
  • 00:03:58 right now I will always get that text
  • 00:04:00 for the API endpoint I'm hitting so I
  • 00:04:03 expect and now I need to import that
  • 00:04:06 function which I want to test so that
  • 00:04:08 will be print title I'm pulling that out
  • 00:04:13 of my exported object in the app.js file
  • 00:04:18 so I expect print title the result of
  • 00:04:22 that function call to be equal to my all
  • 00:04:26 uppercase text here this is my
  • 00:04:28 expectation we can now run that test by
  • 00:04:31 running NPM test and this executes all
  • 00:04:33 test dot J's files and this actually
  • 00:04:37 fails because it fails to execute
  • 00:04:40 addeventlistener off now now the reason
  • 00:04:43 for that actually is that app J s
  • 00:04:46 contains some code that will always run
  • 00:04:49 when this file gets executed and it does
  • 00:04:51 get executed when I import from that
  • 00:04:53 file and it tries to access the Dom here
  • 00:04:56 which just doesn't work in this
  • 00:04:58 environment here the real DOM is not
  • 00:05:00 loaded here so an easy way around that
  • 00:05:03 is to take that function and export it
  • 00:05:06 into a separate file maybe a util dot
  • 00:05:08 J's fall this is also something we did
  • 00:05:11 in the last video already export
  • 00:05:15 and title here and what this allows us
  • 00:05:18 to do is it allows us to import
  • 00:05:20 something from that file only without
  • 00:05:22 executing a purchase and therefore
  • 00:05:24 without hitting the real DOM
  • 00:05:26 so we can rename that testing file here
  • 00:05:29 to util test jeaious and we import print
  • 00:05:33 title from dot slash util and an abscess
  • 00:05:37 since I removed print title from there
  • 00:05:39 we also need to import print title by
  • 00:05:45 requiring it from the dot slash util
  • 00:05:48 file here and now for that to work I'll
  • 00:05:51 also have to move load title over to you
  • 00:05:53 tell Jess because print title depends on
  • 00:05:56 that so let's move load title into that
  • 00:05:59 file as well that means that fetch data
  • 00:06:01 also needs to move in there so from
  • 00:06:04 aptus let's grab the fetch data import
  • 00:06:06 and move that to the util J's file and
  • 00:06:10 now with that setup if we rerun NPM test
  • 00:06:15 here now it should be able to run our
  • 00:06:17 test here and it does but I get an error
  • 00:06:20 that essentially it expected a string
  • 00:06:24 but it received undefined and now this
  • 00:06:26 already shows us a problem we have with
  • 00:06:30 asynchronous code testing well actually
  • 00:06:32 in print title I am indeed returning
  • 00:06:35 nothing so getting undefined as a result
  • 00:06:37 makes perfect sense but even if I would
  • 00:06:40 return something like for example in
  • 00:06:41 here if I not only lock the title but I
  • 00:06:44 also return the title in here if I do
  • 00:06:47 that and I rerun I won't still get that
  • 00:06:50 error I can already say that the reason
  • 00:06:53 for that is that and that has nothing to
  • 00:06:54 do with testing that sin Robel
  • 00:06:56 javascript behavior return statements
  • 00:06:58 inside of callbacks or inside of promise
  • 00:07:01 then calls are not executed or are
  • 00:07:05 executed but JavaScript does not wait
  • 00:07:07 for them it does not return this value
  • 00:07:09 as a return value for this overall
  • 00:07:12 function instead this is the return
  • 00:07:14 value of that inner function here and
  • 00:07:16 we're not testing this inner function
  • 00:07:17 we're testing that overall function so
  • 00:07:21 in our case here one simple solution
  • 00:07:24 would be to test load title here we
  • 00:07:26 return something in the inner function
  • 00:07:28 and we also return something in the main
  • 00:07:30 function we return the whole promise so
  • 00:07:34 chances are that we get back a promise
  • 00:07:36 here too which we then can listen and
  • 00:07:38 that might allow us to expect something
  • 00:07:40 helpful so let's change something let's
  • 00:07:43 export load title here so that we can
  • 00:07:47 test it let's go to you tilt SJS
  • 00:07:50 and there I will not expect something
  • 00:07:53 like that but I'll get my title but
  • 00:07:56 instead I will import load title here
  • 00:08:00 and it will execute load title and this
  • 00:08:03 gives us a promise so I will add then
  • 00:08:05 here and in there I know I'll get my
  • 00:08:08 title because that's exactly the way I
  • 00:08:10 use it in print title right I have loads
  • 00:08:12 title and then I get my title so now I
  • 00:08:15 have the exact same usage here in you
  • 00:08:17 tell test j/s and now here I want to
  • 00:08:19 expect something that my title should be
  • 00:08:23 equal to that whoops to that upper case
  • 00:08:25 tile I got here so we can grab that
  • 00:08:28 title here copy it and that is what I
  • 00:08:32 expect as a value now if I rerun NPM
  • 00:08:36 test it actually passes because now this
  • 00:08:40 test succeeds so this is better this is
  • 00:08:42 how we could test our asynchronous code
  • 00:08:44 but we still have a problem here the
  • 00:08:48 problem is we're still hitting our API
  • 00:08:51 so here in Utah yes I'm using fetch data
  • 00:08:54 and if you remember we're defining that
  • 00:08:56 in the HTTP file so here and they were
  • 00:08:59 making a real API request we typically
  • 00:09:02 don't want to do that in testing we
  • 00:09:04 might exceed certain API quotas if we do
  • 00:09:07 that if we're testing post requests we
  • 00:09:10 might even edit something on the API
  • 00:09:11 which we certainly don't want to do as
  • 00:09:13 part of testing certainly not with our
  • 00:09:16 production API at least so hitting the
  • 00:09:19 API is typically not something we want
  • 00:09:20 to do there also are other reasons
  • 00:09:22 against it what would we achieve by
  • 00:09:25 really making that HTTP request do you
  • 00:09:28 want to test if your API works correctly
  • 00:09:30 if you're building that API you should
  • 00:09:33 do these tests during the API
  • 00:09:35 development not when working on your
  • 00:09:37 front-end these two should be separated
  • 00:09:39 so that is certainly not something you
  • 00:09:41 want to test
  • 00:09:42 if the API works or not is not something
  • 00:09:45 you test in your front-end code you
  • 00:09:47 might test error fall backs or anything
  • 00:09:49 like that but not if the API works
  • 00:09:51 correctly you also don't want to test if
  • 00:09:54 the get method exposed by Axios works
  • 00:09:57 correctly that is a method provided by a
  • 00:10:00 third party package and we rely on that
  • 00:10:02 package doing its job so we also don't
  • 00:10:05 test third party package functionalities
  • 00:10:07 we want to test our own code and our own
  • 00:10:10 code here for example is that we extract
  • 00:10:13 data and that here that we transform
  • 00:10:16 this to application that we pull out the
  • 00:10:18 title now to focus on that we do
  • 00:10:22 something which is called mocking we
  • 00:10:24 mock features which means we replace
  • 00:10:27 features we don't want to test with some
  • 00:10:30 dummy implementations and how it could
  • 00:10:32 this look here well in util test Jas we
  • 00:10:36 got load title and we want to test if
  • 00:10:40 that successfully transforms our title
  • 00:10:43 to be all uppercase to be this text now
  • 00:10:47 to test this what we need to do is we
  • 00:10:50 need to replace fetch data with a mock
  • 00:10:52 now just gives us a great way of
  • 00:10:55 preventing us from using that fetch data
  • 00:10:58 method we can create a new folder called
  • 00:11:01 underscore underscore mocks all
  • 00:11:03 lowercase underscore underscore next you
  • 00:11:07 the files that contain our raw source
  • 00:11:10 code so in this case in our root folder
  • 00:11:12 now here we can't add HTTP dot J's false
  • 00:11:17 so the same file name as the file that
  • 00:11:20 has the function we want to replace and
  • 00:11:22 now in HTTP J's we can now do some magic
  • 00:11:25 to set up some functions that will
  • 00:11:28 replace our real functions when running
  • 00:11:31 the tests for that I'll copy my code
  • 00:11:34 from HTTP I'll get rid of axis though
  • 00:11:37 because I'll not use that here I'll
  • 00:11:38 still export fetch data though but in
  • 00:11:42 fetch data I'll simply return a promise
  • 00:11:45 which I instantly resolved with promise
  • 00:11:47 resolve to an object that has a title
  • 00:11:50 property with the text I want to test
  • 00:11:52 which I'll now convert to all lowercase
  • 00:11:55 though so the lectures
  • 00:11:56 aunt autumn and now that I'm exporting
  • 00:12:03 this here something interesting will
  • 00:12:04 happen in HTTP j/s I added a console log
  • 00:12:08 statement to fetch data to see when
  • 00:12:10 we're hitting this API and when we're
  • 00:12:12 not if I now run NPM test
  • 00:12:20 we are hitting the API as you can see
  • 00:12:23 here we have that console log statement
  • 00:12:24 from the HTTP file in there now we can
  • 00:12:27 go to util test j/s and in there we want
  • 00:12:31 to add just mock at the beginning and
  • 00:12:34 mark the HTTP J's file like this you can
  • 00:12:39 omit touches though now if we run NPM
  • 00:12:42 tests you see it also took close to a
  • 00:12:47 second because it had to do some magic
  • 00:12:49 bandit scenes but you don't see that
  • 00:12:51 console log statement because what now
  • 00:12:53 happens is when this test gets executed
  • 00:12:56 when this testing file gets executed
  • 00:12:58 just goes ahead and replaces the HTTP
  • 00:13:01 file with our mocked file so all the
  • 00:13:04 functions that are exported here are
  • 00:13:07 then replacing the functions we are
  • 00:13:09 normally exporting in the real HTTP file
  • 00:13:12 for our tests only so for the tests only
  • 00:13:15 we use our mocked functions here so this
  • 00:13:17 dummy fetch data which gives us back an
  • 00:13:21 object that allows the other functions
  • 00:13:23 to work fine but which does not hit the
  • 00:13:26 API because we don't want to test the
  • 00:13:28 API response and we don't want to test
  • 00:13:30 the access get method so we can rely on
  • 00:13:33 the API returning as an object that has
  • 00:13:35 a title property and we can rely on the
  • 00:13:38 axis get method giving us an object and
  • 00:13:40 a response which has a data property so
  • 00:13:43 we don't need to test that we want to
  • 00:13:45 test whether our code in the util JS
  • 00:13:48 file correctly extracts the title and
  • 00:13:51 transforms it and that is the case
  • 00:13:53 otherwise this test would not have
  • 00:13:56 succeeded for our marked implementation
  • 00:13:59 of fetch data where we return an object
  • 00:14:02 with a title that is all lowercase so
  • 00:14:05 not what we test for lowercase this text
  • 00:14:08 and therefore since our test succeeds we
  • 00:14:10 know that our transformation here does
  • 00:14:12 work so this is how we can test async
  • 00:14:15 code or how we can replace code how we
  • 00:14:17 can replace functionalities if we don't
  • 00:14:20 wanna in this case hit the API the same
  • 00:14:23 would be the case for let's say code
  • 00:14:25 that accesses the filesystem you don't
  • 00:14:28 want to do that instead you want to mark
  • 00:14:30 such functionalities you can by the way
  • 00:14:33 all the
  • 00:14:33 Mauck global packages like axis itself
  • 00:14:36 instead of marking our fetch data
  • 00:14:40 function you could create access dot JS
  • 00:14:44 file here and there we could export our
  • 00:14:49 own get function here which we of course
  • 00:14:56 have to define because remember in my
  • 00:15:02 HTTP file I am using the get method of
  • 00:15:06 the axis object so here I want to export
  • 00:15:10 a get method and that should be a
  • 00:15:11 function that takes a URL as an argument
  • 00:15:15 but we don't care about that URL here
  • 00:15:18 what we want to do here is we can return
  • 00:15:23 an object which in this case since this
  • 00:15:26 is faking the response from Axios so we
  • 00:15:30 want to fake whatever get gives us back
  • 00:15:33 and that should be a response object
  • 00:15:34 which at least has a data key because
  • 00:15:36 we're extracting that so here in the
  • 00:15:38 axis mark we want to have a data object
  • 00:15:40 which shouldn't turn be an object with
  • 00:15:43 the title we want to test so that
  • 00:15:45 lowercase title we define here
  • 00:15:47 so basically we're now marking a
  • 00:15:49 functionality which is one level above
  • 00:15:52 the last functionality where we mocked
  • 00:15:55 our own function now we're using we're
  • 00:15:59 mocking the function this raw function
  • 00:16:01 so this function depends on so now in
  • 00:16:04 the axis J's mark file I'm returning an
  • 00:16:07 object which has a data key which has a
  • 00:16:08 object as a value which has a title key
  • 00:16:11 which is the lowercase version of our
  • 00:16:13 text and with that being marked we can
  • 00:16:16 go to our util test J's fault we can
  • 00:16:19 comment out this marking of our HTTP
  • 00:16:21 implementation so we'll now not use this
  • 00:16:24 HTTP file we've created a second ago
  • 00:16:27 gestures will automatically use mocks of
  • 00:16:30 global node modules though so x SJS this
  • 00:16:33 mark should be used automatically we
  • 00:16:35 don't have to instruct just to do that
  • 00:16:37 and now we can simply run npm test and
  • 00:16:41 here
  • 00:16:45 it fails because what I returned here is
  • 00:16:50 not a promise and this already proves
  • 00:16:52 that this mocked fowl is being used
  • 00:16:54 otherwise it would not have failed I'm
  • 00:16:57 returning an object there and therefore
  • 00:16:59 calling then fails I of course have to
  • 00:17:02 return a promise here by using promise
  • 00:17:05 resolve so a promise that eventually
  • 00:17:07 yields this object and once I do that
  • 00:17:10 and therefore my macht get method
  • 00:17:13 provides a promise as the real axis
  • 00:17:15 implementation does once I do that it
  • 00:17:18 passes again we see fetching data here
  • 00:17:21 because we are now using my original
  • 00:17:23 fetch data implementation but the get
  • 00:17:26 method we're using here is not the real
  • 00:17:28 one it's our marked one so this one here
  • 00:17:32 and this is how marking works we've just
  • 00:17:35 now below the video you find some
  • 00:17:37 article or some links to the official
  • 00:17:39 Docs with more articles more information
  • 00:17:41 on how mocking can be done with jest but
  • 00:17:44 I hope that this video also was helpful
  • 00:17:45 with understanding why we mark and how
  • 00:17:49 we can mock so how we can replace
  • 00:17:51 certain implementations which we don't
  • 00:17:53 want to test in our code that depends on
  • 00:17:56 them so I hope this was helpful
  • 00:17:58 hopefully see you in future videos – bye