Transcript
00:00 To mock a module in a test, I will use the v.mock utility. This utility accepts two arguments. The first one is the module name, and the second is a factory function. Module name is the name of the module that you want to mock. This can be a local module or a third-party module, like in my case, it's a workshop slash epicsdk.
00:22 I can provide an exact string here, or I can use a dynamic input expression. I will choose the latter because this way, these inputs are type-safe. So, if I make a mistake, TypeScript will let me know about it even before the test to run. The second argument is factory function is responsible for producing the experts of this
00:41 module. So, from the usage, I know that my module here exports a function called query table that I use to query for user. So, I'll add this key here to this experts object. It would be nice if this exported function instead was a mock function, so I'm able to replace its implementation on a test basis and spy on it.
01:01 So, let's assign it to query table mock, and now let's create this mock function. So, to do that, we know that we need to call v.fn. Notice how we're not adding this v.mock in any setup hooks, like before all. That is because this utility is special. It's hoisted. This means that Vitas will place it and run it as the first thing in this test,
01:22 no matter what you do. And because of this, there's a bit of a difficulty here. This factory function won't be able to reference the query table mock at all. It will not exist by the moment this function is run. To fix this, I will use v.hoisted utility. This utility accepts a function, and it's
01:39 a factory function of the things you want to hoist. In other words, kind of put in a box and be able to reference in other hoisted utilities like v.mock. Let's also annotate this mock function that it's a function that returns a promise that resolves to user, because this is what our query table function does.
01:57 With this in place, since we're using this mocked function query table mock, let's make sure to reset all the calls recorded to it. I will add the after each hook and call v.resetAllMocks. This will reset the spies, but also mock implementations that we'll introduce in these tests. And finally, we can start with particular test cases.
02:16 In this one, it should return the authorized user. To do this, I will go to my query table mock and mock its resolve value to a particular user object. I will use this mock user as an example. Note that I'm using mock resolve value and not mock return value because query table
02:33 is an asynchronous function, so this is a shorthand for resolving this function with promise that then resolves to particular data. And finally, I can write an assertion for it by calling authorize with particular ID, for example, abc123, and now I expect it to return the user back as far as I remember.
02:53 Yes, so that is the very basic happy path scenario. Now, let's also recreate a scenario where our authorized function returns null if there was no user found. So, to emulate the scenario where no user is found, we need to mock the implementation of this function to resolve with null.
03:11 And I'll also write the expectation around calling authorize with particular user, and it should resolve to be null. And finally, there's an error case. In this case, actually, querying for the user should throw. To do that, I will use a special helper called mock rejected value.
03:32 So, our query table mock, mock rejected value. Similar to mock resolve value, this is a shorthand for returning a rejected promise from the mock function. And I will use this dummy error as the error that this promise reject with. Now, I can write assertion like this.
03:49 I expect authorize with particular ID to reject, and to throw an error, I will use the same error that I used here, like this. Let's now verify these tests by running npm test. Okay, it looks like I have a failure scenario here, of course, because the error that I provided isn't the actual error that our authorize function throws.
04:12 Instead, it throws a more meaningful error for us, the developer. Failed to fetch the user by ID, including the particular ID. So, let's wrap it in this one. By ID, and ID was ABC 1 to 3. Once I fix this assertion, I can see that all the tests are passing all the three scenarios.
04:31 So, what are we doing here is using v.mock utility to mock a third-party module. By doing that, our test case gains control over that module, in particular, its experts. And bear in mind that because this factory function is the only thing in charge of the module, nothing else except of these experts will ever be exported from that particular module.
04:51 For example, no side effects will run unless we want them to run. Just keep this in mind to include all the experts you need to test your tested code. And since this helper function is hoisted, we're also hoisting our mock function to be able to control the query table behavior in each individual test.
05:09 So then, in different scenarios, we're able to call things like mockResolvedValue to resolve this promise, or mockRejectedValue to reject that promise. And this allows us to see how our authorized function behaves when the third-party module, this query table function, differs in implementation.