Jest: Provide a more useful mock result when requiring an image in React Native

Created on 8 Feb 2017  路  10Comments  路  Source: facebook/jest


Do you want to request a feature or report a bug?
Feature

What is the current behavior?
When requiring an image with Jest and the React Native preset, it just returns 1.
Example:

console.log(require('../../styles/img/ic_menu_black_24dp.png')); // 1

I can manually mock the image:

jest.mock('../../styles/img/ic_menu_black_24dp.png', ()=>'ic_menu_black_24dp.png');

But it's annoying to have to do this for every image.

What is the expected behavior?
Ideally Jest should automatically implement the mock i just exampled above, returning either the filename or the entire path.

Please provide your exact Jest configuration and mention your Jest, node, yarn/npm version and operating system.
Jest version 18.1.0, NPM 4.1.2, React Native 0.39.2, React 15.4.1, Ubuntu 16.10.
I'm also using Enzyme and enzyme-to-json for shallow snapshot testing.

Most helpful comment

Scimonster wrote:

I tried copying in the example code for moduleNameMapper, and it didn't do anything.

I think it doesn't work because React Native jest presets include a stub for images thus overriding your moduleNameMapper. The preset and your config is merged and the resolver chooses either one (don't know if it's the first or last match).

But even if that did work, it looks like it would just return 'test-file-stub' for everything, as there's no function being called, hence no place for customizing in that file.

That's true. I also noticed that the jest.mock workaround only works for one image, it mocks RelativeImageStub, which every image require is replaced with. One solution is to use the transformer setting like the fileTransformer in https://facebook.github.io/jest/docs/webpack.html#mocking-css-modules. But moduleNameMapper in the React Native presets overrides the transformer settings. The only solution I came up with is to copy the React Native presets to your own config and remove the RelativeImageStub and add your own transformer. Here's my jest config from package.json:

  "jest": {
    "haste": {
      "defaultPlatform": "ios",
      "platforms": ["android", "ios", "native"],
      "providesModuleNodeModules": [
          "react-native"
      ]
    },
    "moduleNameMapper": {
      "^React$": "<rootDir>/node_modules/react"
    },
    "transform": {
      "^.+\\.js$": "babel-jest",
      "^[./a-zA-Z0-9$_-]+\\.(bmp|gif|jpg|jpeg|png|psd|svg|webp)$": "<rootDir>/jest/mediaFileTransformer.js"
    },
    "modulePathIgnorePatterns": [
      "<rootDir>/node_modules/react-native/Libraries/react-native/",
      "<rootDir>/node_modules/react-native/packager/"
    ],
    "transformIgnorePatterns": [
      "node_modules/(?!(jest-)?react-native|react-clone-referenced-element)"
    ],
    "testEnvironment": "node",
    "setupFiles": [
      "<rootDir>/node_modules/react-native/jest/setup.js",
    ]
  }

jest/mediaFileTransformer.js:

const path = require('path');

// Mocks every media file to return its filename. Makes it possible to test that
// the correct images are loaded for components.

module.exports = { process: (_, filename) => `module.exports = '${JSON.stringify(path.basename(filename))}';` };

The fix is quite horrible, I would rather stick to using the React Native presets.

All 10 comments

Have you tried moduleNameMapper? Here's example usage.

There was a discussion a while back to have default moduleNameMappers. I'm a bit worried how that might go. @thymikee ever thought about that?

Yes I've seen that discussion with Dan, and I like the idea.

I think we could make it so there are some defaults, which gets totally wiped and overwritten if set explicitly by user.

I tried copying in the example code for moduleNameMapper, and it didn't do anything.

But even if that did work, it looks like it would just return 'test-file-stub' for everything, as there's no function being called, hence no place for customizing in that file.

Scimonster wrote:

I tried copying in the example code for moduleNameMapper, and it didn't do anything.

I think it doesn't work because React Native jest presets include a stub for images thus overriding your moduleNameMapper. The preset and your config is merged and the resolver chooses either one (don't know if it's the first or last match).

But even if that did work, it looks like it would just return 'test-file-stub' for everything, as there's no function being called, hence no place for customizing in that file.

That's true. I also noticed that the jest.mock workaround only works for one image, it mocks RelativeImageStub, which every image require is replaced with. One solution is to use the transformer setting like the fileTransformer in https://facebook.github.io/jest/docs/webpack.html#mocking-css-modules. But moduleNameMapper in the React Native presets overrides the transformer settings. The only solution I came up with is to copy the React Native presets to your own config and remove the RelativeImageStub and add your own transformer. Here's my jest config from package.json:

  "jest": {
    "haste": {
      "defaultPlatform": "ios",
      "platforms": ["android", "ios", "native"],
      "providesModuleNodeModules": [
          "react-native"
      ]
    },
    "moduleNameMapper": {
      "^React$": "<rootDir>/node_modules/react"
    },
    "transform": {
      "^.+\\.js$": "babel-jest",
      "^[./a-zA-Z0-9$_-]+\\.(bmp|gif|jpg|jpeg|png|psd|svg|webp)$": "<rootDir>/jest/mediaFileTransformer.js"
    },
    "modulePathIgnorePatterns": [
      "<rootDir>/node_modules/react-native/Libraries/react-native/",
      "<rootDir>/node_modules/react-native/packager/"
    ],
    "transformIgnorePatterns": [
      "node_modules/(?!(jest-)?react-native|react-clone-referenced-element)"
    ],
    "testEnvironment": "node",
    "setupFiles": [
      "<rootDir>/node_modules/react-native/jest/setup.js",
    ]
  }

jest/mediaFileTransformer.js:

const path = require('path');

// Mocks every media file to return its filename. Makes it possible to test that
// the correct images are loaded for components.

module.exports = { process: (_, filename) => `module.exports = '${JSON.stringify(path.basename(filename))}';` };

The fix is quite horrible, I would rather stick to using the React Native presets.

You can send a PR to react native. This is quite interesting. We could also include this somewhere in our docs

For an app I'm working on I ran into this problem. We have one images module where we'd pull images we need. Before we used jest, we exported all of our images as one object and pulled the images we needed from the object. I turned the module into an images function which I could then create a jest mock for:

export default function images(name) {
const imageList = {
  logo: require('./findme-logo_192-192.png'),
  avatar: {
      adod: require('./avatar_adod_alert.jpg'),
      scg: require('./[email protected]')
  },
  layer: {
      alert: require("./layer_safezone_alert.png"),
      normal: require("./layer_safezone_normal.png"),
      pressed: require("./layer_safezone_pressed.png")
  },
  battery: {
    zero: require("./battery_00_96px.png"),
    ten: require("./battery_10_96px.png"),
    twenty: require("./battery_20_96px.png"),
    thirty: require("./battery_30_96px.png"),
    fourty: require("./battery_40_96px.png"),
    fifty: require("./battery_50.png"),
    eighty: require("./battery_80_96px.png"),
    ninety: require("./battery_90_96px.png"),
    full: require("./battery_100_96px.png")
  },
  floor: require("./floor_level.jpeg"),
  noImage: require('./noimage.png')
}
let image = imageList;
name.split('.').forEach((name) => { image = image[name] })
return image;   
}

And then in my jest config file:

jest.mock('../app/images/images.js', () => jest.fn())

Just adding to the excellent workaround by @myrjola.

The provided regex works on macOS, but we had issues on Windows. The following works on both:

^.+\\.(bmp|gif|jpg|jpeg|png|psd|svg|webp)$
Was this page helpful?
0 / 5 - 0 ratings

Related issues

ticky picture ticky  路  3Comments

mmcgahan picture mmcgahan  路  3Comments

jardakotesovec picture jardakotesovec  路  3Comments

hramos picture hramos  路  3Comments

kgowru picture kgowru  路  3Comments