Mocking

    Say we have two functions, and multiply, if we want to assert that the multiply function is called during execution of the square function we need a way to spy on the multiply function. There are a few ways to achieve this with Spies, one is to have the square function take the multiply multiply as a parameter.

    This way, we can call square(multiply, value) in the application code or wrap a spy function around the multiply function and call square(multiplySpy, value) in the testing code.

    1. // https://deno.land/std@$STD_VERSION/testing/mock_examples/parameter_injection_test.ts
    2. import {
    3. assertSpyCall,
    4. assertSpyCalls,
    5. spy,
    6. } from "https://deno.land/std@$STD_VERSION/testing/mock.ts";
    7. import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
    8. import {
    9. multiply,
    10. square,
    11. } from "https://deno.land/std@$STD_VERSION/testing/mock_examples/parameter_injection.ts";
    12. Deno.test("square calls multiply and returns results", () => {
    13. const multiplySpy = spy(multiply);
    14. assertEquals(square(multiplySpy, 5), 25);
    15. // asserts that multiplySpy was called at least once and details about the first call.
    16. assertSpyCall(multiplySpy, 0, {
    17. args: [5, 5],
    18. returned: 25,
    19. });
    20. // asserts that multiplySpy was only called once.
    21. assertSpyCalls(multiplySpy, 1);
    22. });

    If you prefer not adding additional parameters for testing purposes only, you can use spy to wrap a method on an object instead. In the following example, the exported _internals object has the multiply function we want to call as a method and the square function calls _internals.multiply instead of multiply.

    1. // https://deno.land/std@$STD_VERSION/testing/mock_examples/internals_injection_test.ts
    2. import {
    3. assertSpyCall,
    4. assertSpyCalls,
    5. spy,
    6. } from "https://deno.land/std@$STD_VERSION/testing/mock.ts";
    7. import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
    8. import {
    9. _internals,
    10. square,
    11. } from "https://deno.land/std@$STD_VERSION/testing/mock_examples/internals_injection.ts";
    12. const multiplySpy = spy(_internals, "multiply");
    13. try {
    14. assertEquals(square(5), 25);
    15. } finally {
    16. // unwraps the multiply method on the _internals object
    17. multiplySpy.restore();
    18. }
    19. // asserts that multiplySpy was called at least once and details about the first call.
    20. assertSpyCall(multiplySpy, 0, {
    21. args: [5, 5],
    22. returned: 25,
    23. });
    24. // asserts that multiplySpy was only called once.
    25. assertSpyCalls(multiplySpy, 1);
    26. });

    One difference you may have noticed between these two examples is that in the second we call the restore method on multiplySpy function. That is needed to remove the spy wrapper from the _internals object’s multiply method. The restore method is called in a finally block to ensure that it is restored whether or not the assertion in the try block is successful. The restore method didn’t need to be called in the first example because the multiply function was not modified in any way like the _internals object was in the second example.

    Say we have two functions, randomMultiple and randomInt, if we want to assert that randomInt is called during execution of randomMultiple we need a way to spy on the randomInt function. That could be done with either of the spying techniques previously mentioned. To be able to verify that the randomMultiple function returns the value we expect it to for what randomInt returns, the easiest way would be to replace the randomInt function’s behavior with more predictable behavior.

    You could use the first spying technique to do that but that would require adding a randomInt parameter to the randomMultiple function.

    Say we want to verify it returns correct values for both negative and positive random integers. We could easily do that with stubbing. The below example is similar to the second spying technique example but instead of passing the call through to the original randomInt function, we are going to replace randomInt with a function that returns pre-defined values.

    The mock module includes some helper functions to make creating common stubs easy. The returnsNext function takes an array of values we want it to return on consecutive calls.

    1. // https://deno.land/std@$STD_VERSION/testing/mock_examples/random_test.ts
    2. import {
    3. assertSpyCall,
    4. assertSpyCalls,
    5. returnsNext,
    6. stub,
    7. } from "https://deno.land/std@$STD_VERSION/testing/mock.ts";
    8. import { assertEquals } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts";
    9. import {
    10. _internals,
    11. randomMultiple,
    12. } from "https://deno.land/std@$STD_VERSION/testing/mock_examples/random.ts";
    13. Deno.test("randomMultiple uses randomInt to generate random multiples between -10 and 10 times the value", () => {
    14. const randomIntStub = stub(_internals, "randomInt", returnsNext([-3, 3]));
    15. try {
    16. assertEquals(randomMultiple(5), 15);
    17. } finally {
    18. // unwraps the randomInt method on the _internals object
    19. randomIntStub.restore();
    20. }
    21. // asserts that randomIntStub was called at least once and details about the first call.
    22. assertSpyCall(randomIntStub, 0, {
    23. args: [-10, 10],
    24. returned: -3,
    25. });
    26. // asserts that randomIntStub was called at least twice and details about the second call.
    27. assertSpyCall(randomIntStub, 1, {
    28. args: [-10, 10],
    29. returned: 3,
    30. });
    31. // asserts that randomIntStub was only called twice.
    32. assertSpyCalls(randomIntStub, 2);
    33. });

    Say we have a function that has time based behavior that we would like to test. With real time, that could cause tests to take much longer than they should. If you fake time, you could simulate how your function would behave over time starting from any point in time. Below is an example where we want to test that the callback is called every second.

    1. // https://deno.land/std@$STD_VERSION/testing/mock_examples/interval_test.ts
    2. import {
    3. assertSpyCalls,
    4. spy,
    5. } from "https://deno.land/std@$STD_VERSION/testing/mock.ts";
    6. import { FakeTime } from "https://deno.land/std@$STD_VERSION/testing/time.ts";
    7. import { secondInterval } from "https://deno.land/std@$STD_VERSION/testing/mock_examples/interval.ts";
    8. Deno.test("secondInterval calls callback every second and stops after being cleared", () => {
    9. const time = new FakeTime();
    10. try {
    11. const cb = spy();
    12. const intervalId = secondInterval(cb);
    13. assertSpyCalls(cb, 0);
    14. time.tick(500);
    15. assertSpyCalls(cb, 0);
    16. time.tick(500);
    17. assertSpyCalls(cb, 1);
    18. time.tick(3500);
    19. assertSpyCalls(cb, 4);
    20. clearInterval(intervalId);
    21. time.tick(1000);
    22. assertSpyCalls(cb, 4);
    23. } finally {
    24. time.restore();