Keith Jackson
Keith Jackson

Reputation: 3269

Why is one of my my JEST mocks not working when the other works fine?

I have defined 2 JEST mocks. The problem I am getting. The first mock doesn't work but the second does.

import Helper from '../../test-helper'

import Storage from '@/storage'
import GuestContext from '@/auth/guest-context'
import UserContext from '@/auth/user-context'

// import LocalStorageGateway from '@/storage/local/local-storage-gateway'
const mockContextUpsert = jest.fn()

jest.mock('@/storage/local/local-storage-gateway', () => {
    return jest.fn().mockImplementation((authContext) => {
        return {
            authContext,
            context: {
                upsert: mockContextUpsert
            }
        }
    })
})

// import RemoteStorageGateway from '@/storage/remote/remote-storage-gateway'
const mockFetch = jest.fn()

jest.mock('@/storage/remote/remote-storage-gateway', () => {
    return jest.fn().mockImplementation((authContext) => {
        return {
            authContext,
            fetch: mockFetch
        }
    })
})

I have tried...

The error I am getting is...

ReferenceError: mockContextUpsert is not defined

I cannot understand why the first mock doesn't work when the second mock works perfectly.

This will also work, adding the mock variables to the second declaration (not of any use as they're different classes but for reference)...

const mockContextUpsert = jest.fn()
const mockFetch = jest.fn()

jest.mock('@/storage/remote/remote-storage-gateway', () => {
    return jest.fn().mockImplementation((authContext) => {
        return {
            authContext: authContext,
            fetch: mockFetch,
            context: {
                upsert: mockContextUpsert
            }
        }
    })
})

The classes being mocked here are almost identical.

UPDATE

Removing the reference to GuestContext() [this is additional complication, has since been removed, and is confusing the actual problem the question is trying to ask]

Upvotes: 0

Views: 3738

Answers (2)

Keith Jackson
Keith Jackson

Reputation: 3269

I have solved this eventually - The issue here wasn't with the mocks themselves but the class being mocked.

The LocalStorageGateway class is used to create a private instance within an imported module Storage, as follows...

const guestLocal = new LocalStorageGateway(new GuestContext())

This static context is causing the mocked constructor to execute before the variables are defined as Storage is one of the first module imported.

There are several ways around this...

You can change this...

import Helper from '../../test-helper'

import Storage from '@/storage'
import GuestContext from '@/auth/guest-context'
import UserContext from '@/auth/user-context'

(insert mocks here)

to...

const mockContextUpsert = jest.fn()

import Helper from '../../test-helper'

import Storage from '@/storage'
import GuestContext from '@/auth/guest-context'
import UserContext from '@/auth/user-context'

for example (yuk?).

Alternatively you can wrap mockContextUpsert up in a wrapping function (my earlier not-really-good-enough answer) -> This feels a bit tidier to me.

const mockContextUpsert = jest.fn()
jest.mock('@/storage/local/local-storage-gateway', () => {
    return jest.fn().mockImplementation((authContext) => {
        return {
            authContext: authContext,
            context: {
                upsert: (cxt) => {
                    mockContextUpsert(cxt)
                }
            }
        }
    })
})

I could also make guestLocal a function but I don't really want to do that and create new gateway instances every time I want to use it (It's why it's there in the first place). If Storage was a class it would be instantiated in the constructor but it isn't and has no real need to be.

Thanks to @Teneff for his input on this, his answer got my brain looking at it the right way around. the variables are not hoisted was key - They work differently - I was operating on an incorrect understanding that anything called mockXXXX would be hoisted up above the mock calls but they aren't they are just 'allowed'.

Upvotes: 1

Teneff
Teneff

Reputation: 32148

A quote from the jest documentation:

Jest will automatically hoist jest.mock calls to the top of the module (before any imports)

meaning that whenever you try to define the function being returned by the mock mockGuestContext is not yet defined

What you can do is create an auto-mock

import localStorageGateway from '@/storage/local/local-storage-gateway';
jest.mock('@/storage/local/local-storage-gateway');

// and since localStorageGateway is a function 
// jest will automatically create jest.fn() for it
// so you will be able to create authContext
const authContext = new GuestContext();

// and use it within the return value
localStorageGateway.mockReturnValue({
  authContext,
  context: {
    upsert: jest.fn(),
  }
})

Upvotes: 0

Related Questions