'Jest: mock out Expo releaseChannels

Been trying trying out a lot of things but without luck. I'm trying to test out my configuration for different release channels in expo.

My latest attempt looks like this:

import * as Updates from 'expo-updates'
import { Platform } from 'react-native'

export const BUILD_PROFILES = ['local', 'development', 'preview', 'production'] as const
type BuildProfile = typeof BUILD_PROFILES[number]

export default function Configuration() {
  type EnvironmentVariables = {
    API_URL: string
  }
  console.log({ releaseChannel: Updates.releaseChannel })
  switch (Updates.releaseChannel as BuildProfile) {
    case 'local': {
      const env: EnvironmentVariables = {
        API_URL: Platform.OS === 'android' ? 'http://10.0.2.2:3001' : 'http://localhost:3001',
      }
      return env
    }

    case 'development': {
      const env: EnvironmentVariables = {
        API_URL: 'http://test.company.com',
      }

      return env
    }

    default: {
      const env: EnvironmentVariables = {
        API_URL: 'http://test.company.com',
      }

      return env
    }
  }
}
import { Platform } from 'react-native'

import * as CONFIG from '.'

const easJson = require('~/eas.json')

describe('Config.ts', () => {
  afterEach(() => {
    jest.restoreAllMocks()
  })

  it('has all the build profiles in eas["build"]', () => {
    Object.keys(easJson.build).forEach((key) => {
      expect(CONFIG.BUILD_PROFILES).toContain(key)
    })
  })

  describe('Development', () => {
    describe('Locally (on the MacBook)', () => {
      beforeEach(() => {
        jest.spyOn('expo-updates', 'releaseChannel', 'get').mockReturnValue('local')
      })

      describe('iPhone Simulator', () => {
        beforeEach(() => {
          jest.mock('react-native/Libraries/Utilities/Platform', () => ({
            OS: 'ios',
            select: () => null,
          }))
          Platform.OS = 'ios'
        })

        it('has API_URL with localhost', () => {
          expect(CONFIG.default().API_URL).toEqual('http://localhost:3001')
        })
      })

      describe('Android Emulator', () => {
        beforeEach(() => {
          jest.mock('react-native/Libraries/Utilities/Platform', () => ({
            OS: 'android',
            select: () => null,
          }))
          Platform.OS = 'android'
        })

        it('has API_URL WITHOUT localhost', () => {
          expect(CONFIG.default().API_URL).toEqual('http://10.0.2.2:3001')
        })
      })

      describe('web browser', () => {
        beforeEach(() => {
          jest.mock('react-native/Libraries/Utilities/Platform', () => ({
            OS: 'web',
            select: () => null,
          }))
          Platform.OS = 'web'
        })

        it('has API_URL with localhost', () => {
          expect(CONFIG.default().API_URL).toEqual('http://localhost:3001')
        })
      })
    })
  })
})

But it seems to still just do

console.log
    { releaseChannel: 'default' }

My jest configuration looks like:

/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
  preset: 'jest-expo',
  rootDir: '../../',
  setupFilesAfterEnv: ['./test/jest/setup.js'], // Setup life-cycle for jest tests.
  automock: false, // it means it will not run the __mocks__ folder by default (so we have to manually mock it in each test or in the setup.js file)
  clearMocks: true, // https://marek-rozmus.medium.com/jest-mock-and-spy-mockclear-vs-mockreset-vs-mockrestore-f52395581950
  moduleDirectories: ['node_modules'],
  modulePathIgnorePatterns: ['<rootDir>/.fttemplates/*'],
  moduleNameMapper: {
    // scape out non-supported NodeJS files
    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
      '<rootDir>/test/jest/assetsTransformer.js',
    '\\.(css|scss|less)$': '<rootDir>/test/jest/assetsTransformer.js',
  },
  transformIgnorePatterns: [
    // transpiling non-transpiled packages
    'node_modules/(?!(jest-)?react-native|@react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base)',
  ],
  collectCoverage: true,
  collectCoverageFrom: [
    '**/*.{js,jsx,ts,tsx}',
    // Don't include the following
    '!.*.{js,jsx,ts,tsx}',
    '!**/coverage/**',
    '!**/node_modules/**',
    '!**/.fttemplates/**',
    '!**/babel.config.{js,ts}',
    '!**/jest.config.{js,ts}',
    '!**/jest.setup.{js,ts}',
    '!**/prettier.config.{js,ts}',
  ],
}

Update

found out that putting it at the top does mock it out

jest.mock('expo-updates', () => ({
  releaseChannel: 'local',
}))

describe('Config.ts', () => {
  afterEach(() => {
    jest.restoreAllMocks()
  })

  ...

but then I'm not able to change it from 'local' to 'development' for my other test cases



Solution 1:[1]

Finally figured it out:

/* eslint-disable import/namespace */
import * as Updates from 'expo-updates'
import { Platform } from 'react-native'

import * as CONFIG from '.'

const easJson = require('~/eas.json')

jest.mock('expo-updates', () => ({
  __esModule: true,
  releaseChannel: null,
}))

describe('Config.ts', () => {
  afterEach(() => {
    jest.restoreAllMocks()
  })

  it('has all the build profiles in eas["build"]', () => {
    Object.keys(easJson.build).forEach((key) => {
      expect(CONFIG.BUILD_PROFILES).toContain(key)
    })
  })

  describe('Development', () => {
    describe('Locally (on the MacBook)', () => {
      beforeEach(() => {
        // @ts-ignore
        Updates.releaseChannel = 'local'
      })

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Norfeldt