Serious Jest: Making Sense of Hoisting
Published: 2022-05-24 19:16:37

**“The ticket’s almost done, I just need to write a few tests”**
Later . . . 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yxj48xu4oibjolhm21we.gif)


## Hoisting Isn’t Always Hoisting

Even if you’re using Jest, you may not have thought too much about how Jest actually replaces your module with a mock. At least until you’re mocks don’t work. 

Jest creates mocks through a process they refer to as hoisting. If you’ve used Javascript long enough you might assume you know what that means. But something I haven't seen elsewhere hoisting in Jest and hoisting in Javascript isn’t the same thing. In this article, I’ll explain both types of hoisting and walk through what happens each time your run a test.

## Upcoming

1. Four ways we might mock a component
2. Jest Hoisting is Execution Order Manipulation
3. Javascript Hoisting is Reference Assignment Manipulation
4. Six Stages of Running a test
5. Four ways to mock a component revisited

## Four Ways We Might Mock A Component

For the sake of example, we’ll make a component called `MoviePoster`. Take a look at it below:

```javascript
// MoviePoster.jsx
import { useDolphLundgren } from "ThunderGunExpress";
export function MoviePoster() {
    const ThunderGun = useDolphLundgren();
    return (
        <PosterContent>
          <ThunderGun />
        </PosterContent>
    )
}

// MoviePoster.test.jsx
import { useDolphLundgren } from "ThunderGunExpress";
describe("HomePage", () => {
    it("Should not hesistate", () => {
      // ... 
    });
});
```

`MoviePoster` uses `ThunderGunExpress` to make an API call through a hook called `useDolphLundgren`. `useDolphLundgren`, returns a component we can pass as a child to a `PosterContent`. But our test suite can’t reproduce the output from the `useDolphLundgren` hook so we'll need to mock it. Let’s look at some ways in which we might mock that hook.

### Jest Best Guess
Let’s look at four examples of how we might mock out `useDolphLundgren` using Jest’s factory mocks. Take a look at each example, guess whether it will work, and then guess why

**Ex 1: Inline mock**

```javascript
jest.mock("ThunderGunExpress", () => ({
    useDolphLundgren: () => <></>,
}));
```

**Ex 2: Passing a function declaration**
```javascript
function MockHook(props) {
    return <></>
};
jest.mock("ThunderGunExpress", () => ({
    useDolphLundgren: MockHook,
}));
```

**Ex 3: Passing a function expression**
```javascript
const MockHook = (props) => {
    return <></>
};
jest.mock("ThunderGunExpress", () => ({
    useDolphLundgren: MockHook,
}));
```

**Ex 4: Calling a function expression within another function expression**
```javascript
const MockHook = (props) => {
    return <></>
};
jest.mock("ThunderGunExpress", () => ({
    useDolphLundgren: () => MockHook(),
}));
```

### Jest Best Guess Addressed
**Example 1 fails** with an error telling you that you can’t rely on that fragment you’ve imported to exist.

**Example 2 works** because the function definition is hoisted and exists when the `useDolphLundgren` mock gets created.

**Example 3 fails** because the `MockHook` variable is undefined when the mock function is created.

**Example 4 works** because the function expression calling `MockHook()` isn’t evaluated until it's invoked and at that time `MockHook` has been initialized. 

And there you have it, Jest mocks demystified. Thanks for reading and remember to . . . oh, it's not clear why those work? Are you the type to get upset when you see a library as an answer to a StackOverflow question?

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vw9pvukarouz5xjygsje.png)
 

Before we can dig into what Jest is doing with these mocks we need to understand a bit about how Jest and Javascript actually run code.

# Hoisting in Jest is Execution Order Manipulation

The Jest docs say Jest creates mocks through hoisting. But what Jest is doing is manipulating the code execution order. The Jest testing finds those `mock()` calls in a file and execute them before any of the import statements. So it doesn’t matter where you put your mock in a test file, Jest will “hoist” it above the import statements. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dqu3gvgdjg1do4kp1y63.gif)

## Hoisting in Javascript is Reference Assignment Manipulation

So while Jest actually changes the order your code runs, Javascript doesn’t. Hoisting is what the Javascript engine does before executing any code at all. The Javascript engine first runs through your code to create what's called an Execution Context. The process of creating an execution context is beyond the scope of this article, but I’ll review the relevant parts. 

### Javascript Execution Context Summary

- The Javascript engine runs your code twice, creating an Execution Context from it and then executing it
- The Execution Context has an identifier for every variable, function, and class
- During the creation phase, the Javascript engine only assigns references in memory to function declarations and classes. It leaves everything else undefined
- Function expressions differ from function declarations because function expressions are (1) undefined until you initialize them and (2) evaluated only when you invoke them

In other words, **hoisting in Javascript means some variables get references to objects in memory before code execution happens.**

### Javascript Execution Context In-Depth

The Execution Context is a stack frame that gets created every time you call a function. During the creation stage, the Javascript engine runs through your code, grabs all the variables, functions, and classes, puts their name at the top of the scope (we'll call these names _identifiers_) and does things with them. What things the engine does depends on what it's looking at. 

If the engine finds a class or function declaration, it takes the definition, adds it to the heap, and then points the identifier to that definition in memory. I.e. each identifier gets a reference to the definition.

If the engine finds a variable, it marks it undefined and moves on. It doesn’t matter if you defined the variable with var, const, or let because the Javascript engine still leaves them `undefined`.

Function expressions are like variables with an extra step: the function body isn’t evaluated until you invoke the function expression. We’ll cover this in more detail later. 

After creating the execution context, the engine does a second pass on your code and evaluates it line by line. Look at the examples below to see how the Execution Context (EC) influences the output of our code:

```javascript
// Variable isn't declared, so doesn't exist at all in the EC
console.log(needsDeclaration)

// Variable declared, but left undefined in the EC during creation phase
console.log(needsInitialization)
var needsInitialization = 5;

// Function defintion assigned during the creation phase, so you can call it before defining it
hoistedFoo()
function hoistedFoo(){...}

// Function expressions are just like variables, they are undefined until initialized
unhoistedBar()
var unhoistedBar = () => {...}

// Const and let are the same as var, but will raise an error to avoid accessing an undefined variable
console.log(alsoNeedsInitialization)
const alsoNeedsInitialization = 5
```

To recap, Jest hoists `mock()` statements by manipulating the execution order, while Javascript hoists memory allocation by manipulating when it assigns references. The tricky part is that both of these ‘hoistings’ happen when you run a test: Javascript will manipulate your references during the creation phase, and then during the execution phase Jest will manipulate the order your code executes in. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/juomqeextnvn1xb1u2pf.gif)
 

# Six Stages of Running A Jest Test

**Stage 0:** Javascript creation phase runs and sets up the Execution Context

**Stage 1:** Mocks are executed

- Jest finds the `jest.mock()` calls and invokes those functions before the `import` statements occur. 
- Mocks are created during this stage, but the function expression’s passed to `mock()` are not yet evaluated.
- This is happening during the Javascript engine’s execution phase, not its creation phase.

**Stage 2: Import Calls are executed**

- Jest works down the component tree of each `import` statement before moving on to the next.
- If Jest finds a mock, it will import that instead of the module.
- The factory expression you passed to `mock()`gets evaluated here. *Whatever is in the Execution Context at this stage is what gets used for that factory expression.* 

**Stage 3: Jest collects the blocks, but doesn't execute them**

- Jest runs through the `describe` and `it` blocks in your test, but it doesn’t evaluate the function expressions you passed in.

**Stage 4: Javascript executes the rest of the file**

- Whatever is defined below the `describe` and `it` blocks gets evaluated. This means the Execution Context your tests run in can be updated after the tests are written

**Stage 5: Jest runs the tests**

## Revisiting the Examples

Now that we've covered beginning to end what happens when we run a test file, let's revisit the original examples. I’ll include the code again to save you from going back up:

**Ex 1: Inline mock**

```javascript
jest.mock("ThunderGunExpress", () => ({
    useDolphLundgren: () => <></>,
}));
```

This code raises an error because the mock will run during Stage 1 but the fragment would be imported during Stage 2. So Jest raises an error to remind you that the fragment may not exist.

**Ex 2: Passing a function declaration**

```javascript
function MockHook(props) {
    return <></>
};
jest.mock("ThunderGunExpress", () => ({
    useDolphLundgren: MockHook,
}));
```

This works because the function definition for `MockHook` gets added to memory and referenced to the `MockHook` identifier during Stage 0. When the mock gets created in Stage 1, Jest replaces the module with the function definition referenced by `MockHook`. 

**Ex 3: Passing a function expression**

```javascript
const MockHook = (props) => {
    return <></>
};
jest.mock("ThunderGunExpress", () => ({
    useDolphLundgren: MockHook,
}));
```

This fails because Jest is hoisting the call to mock and so `MockHook` gets moved into the [Temporal Dead Zone](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#temporal_dead_zone_tdz). The code actually executes like this:

```javascript
jest.mock("ThunderGunExpress", () => ({
    useDolphLundgren: MockHook,
}));

const MockHook = (props) => {
    return <></>
};
```

**Ex 4: Calling the function expression mock within another function expression**

```javascript
const MockHook = (props) => {
    return <></>
};
jest.mock("ThunderGunExpress", () => ({
    useDolphLundgren: props => MockHook(props),
}));
```

Line 5 looks like it should be identical to the same line in Example 3. But **there’s a difference in when the variable** MockHook **is accessed**. The timing for when a variable is accessed is subtle enough that it deserves some more attention.

### **Variable Access Timing**

Let’s take Jest out of the equation and dig a little deeper into how function expressions get evaluated.

```javascript
// needsInitializing is in the Temporal Deadzone
const foo = {
  bar: needsInitializing
}
const needsInitializing = () => console.log("initialized")
foo.bar()
```

The Javascript engine checks the value of `needsInitializing` when attempting to assign it to bar during the creation phase. But that means `needsInitializing` **is accessed before it is defined**. Compare this to the example below:

```javascript
// The expression at line 3 isn't evaluated until line 6
const foo = {
  bar: () => needsInitializing()
}
const needsInitializing = () => console.log("initialized")
foo.bar()
```

This time, the Javascript engine **accesses** needsInitializing **during the execution phase when** foo.bar() **is invoked**. The difference is that during the creation phase Javascript engine doesn’t attempt to evaluate the function expression in line 2 and, so, never tries to access the needsInitializing variable.

### **Ex 4: One Last Time**

```javascript
// The expression at line 6 isn't evaluated until `useDolphLundgren` is called
const MockHook = () => {
    return <></>
};
jest.mock("ThunderGunExpress", () => ({
    useDolphLundgren: () => MockHook(),
}));
```

**Stage 0: Creation**

- `MockHook` is `undefined`,
- `(props) => MockHook(props)` is unevaluated
- `MockHook` is *not accessed*. 

**Stage 1**: **Mocks executes**

- Jest moves the execution order of `jest.mock()` above the `const MockHook` initialization to mock the `useDolphLundgren` module.

**Stage 2**: **Import calls executed**

**Stage 3**: **Blocks collected**

**Stage 4**: **Rest of test file executed**

-  `MockHook` is initialized

**Stage 5**: **Tests Run**

- `MoviePoster` invokes `useDolphLundgren()` while rendering
- Jest gets the mock component from `useDolphLundgren`  
- `() => MockHook()` gets evaluated
- `MockHook` was defined in Stage 4 so the mock gets added. 

**Stage 6: Success**

- Use your Javascript skills to impress family and friends

<img width="100%" style="width:100%" src="https://media.giphy.com/media/QZn4Lj79ms8BWUWyzB/giphy.gif">

## Conclusion

Oftentimes we hit difficulties coding because we make unspoken and inaccurate assumptions. Its easy to assume that Jest and Javascript mean the same thing when they talk about hoisting. Jest, however, uses hoisting to mean manipulating the code execution order and Javascript uses hoisting to mean manipulating when objects in memory get references. I haven't covered how I tested those assumptions though. My first draft did include the steps I used to tease out the information here, but the length grew out of hand. I'll include in a follow up article the process I used to figure out the information here. 

## About Jobber

We're hiring for remote positions across Canada at all software engineering levels! 

Our awesome Jobber technology teams span across Payments, Infrastructure, AI/ML, Business Workflows & Communications. We work on cutting edge & modern tech stacks using React, React Native, Ruby on Rails, & GraphQL. 

If you want to be a part of a collaborative work culture, help small home service businesses scale and create a positive impact on our communities, then visit our [careers ](https://getjobber.com/about/careers?utm_source=devto&utm_medium=social&utm_campaign=eng_blog)site to learn more!