RealOrangeOne / react-native-mock

A fully mocked and test-friendly version of react native (maintainers wanted)
MIT License
571 stars 153 forks source link

What about images? #11

Closed GantMan closed 8 years ago

GantMan commented 8 years ago

I have a file where I require images, but I get the error:

Error: Cannot find module '../Images/Logo/some_image.png'
    at Function.Module._resolveFilename (module.js:338:15)

any way we can mock this?

Jeiwan commented 8 years ago

I used https://github.com/mfncooper/mockery to solve the same issue. It allows you to globally mock require, so you can do:

mockery.enable();
mockery.registerMock('../Images/Logo/some_image.png', 'nice image');

In your tests and this will affect all the files that require '../Images/Logo/some_image.png'. require('../Images/Logo/some_image.png') then will return 'nice image'.

Another similar tool is https://github.com/thlorenz/proxyquire

GantMan commented 8 years ago

thanks!

lelandrichardson commented 8 years ago

This is a great question. Perhaps we could build a method where you can pass it a glob string for images and it will automatically register them as image objects in the require cache.

Open to hear anyone else's suggestions as well...

iyawnis commented 8 years ago

If I use mockery tests are working, but instead of a dir error I was getting the following:

Unexpected character '�' (1:0)
  SyntaxError: test/images/reactNe.png: Unexpected character '�' (1:0)
  > 1 | �PNG
      | ^
    2 | 
    3 | 
    4 | IHDR3�W�SgAMA��
                       �asRGB��� cHRMz&�����u0�`:�p��Q<bKGD�������  pHYsHHF�k>n�IDATx���u�V�ڇ���A���������X����BQ��cwca�b���
      at Parser.pp.raise (node_modules/babylon/index.js:1425:13)
Jeiwan commented 8 years ago

@latusaki you cannot require an image in JS. React Native seems to have some kind of a wrapper around require that catches all paths pointing to an image and processes them in a correct way. If you mock image requiring, you should just return a string that somehow relates to the required file (its name, for example), so you could test this string in a component and check that a correct file was required.

jdebbink commented 8 years ago

@Jeiwan How are you getting mockery to work? I am getting the same error as @latusaki .

In my module I have: <Image source={require('./images/temp.png')}/>

In my test file I have:

var mockery = require('mockery');

mockery.enable();
mockery.registerMock('./images/temp.png', 'temp.png');
const Temp = require('../Temp.js');
mockery.disable();

describe('<Temp/>', () => {
  ...
});
iyawnis commented 8 years ago

I guess you have tried not disabling mockery?

jdebbink commented 8 years ago

@latusaki That worked, thanks!

iyawnis commented 8 years ago

If I remember correct the effect of mockery does not persist between test cases, so I have just added it like this:

  before(() => {
    mockery.enable();
    mockery.registerMock('../../../images/logo-pink.png', 'logo-ping.png');
  });
slamus commented 8 years ago

Hey Guys,

Thanks for your help I've been able to fix the require problem. However, none of your solutions worked for me because required propType for Image.source is either number or shape (?) So, my solution was this:

import mockery from "mockery";

mockery.enable();
mockery.registerMock('../img/myimage.png', 0)
RealOrangeOne commented 8 years ago

@slamus The best way to mock an image objects source would be to pass it something it's expecting.

mockery.registerMock('../img/myimage.png', {{ uri: 'myimage.png' }});

The image doesnt have to exist or anything, but it's a better way of mocking it than just passing in a random value

Sh3rawi commented 8 years ago

I ran into the same issue today, and what i did was the following (im using jest): node_modules/babel-jest/src/index.js:

  process(src, filename) {
    if (babel.util.canCompile(filename)) {
      return babel.transform(src, {
        auxiliaryCommentBefore: ' istanbul ignore next ',
        filename,
        presets: [jestPreset],
        retainLines: true,
      }).code;
    }
    return src;
  },

but it can't compile images, so it returns the source (which is the image itself). All my images are pngs, so i added the following check:

    if (filename.match(/\.png$/)) {
      const w = filename.match(/(.+\/)(.+png$)/)[2];
      const source = `module.exports = '${w}';`;
      return babel.transform(source, {
        auxiliaryCommentBefore: ' istanbul ignore next ',
        filename,
        presets: [jestPreset],
        retainLines: true,
      }).code;
    }

So i return a string for every image require i have, which is very convenient for testing: i shallow render the component, get a reference to the image and check the source prop string to be the right image name.

benjick commented 8 years ago

Hey, trying your solutions here, but not sure what I'm doing wrong.

test/Button.js

import React, { View, Text, StyleSheet } from 'react-native';
import mockery from "mockery";
import { shallow } from 'enzyme';
import { expect } from 'chai';

mockery.enable();
mockery.registerMock('../src/Theme/assets/closeWhite.png', { uri: 'myimage.png' }); //

import { CloseButton } from '../src';

describe('<CloseButton />', () => {
  it('should render stuff', () => {
    const wrapper = shallow(<CloseButton />);
    expect(wrapper.length).to.equal(1);
  });
});

/*
/node_modules/babel-core/lib/transformation/file/index.js:556
      throw err;
      ^

/src/Theme/assets/closeWhite.png: Unexpected character '�' (1:0)
> 1 | �PNG
    | ^
*/
RealOrangeOne commented 8 years ago

@benjick Can you post the source of the CloseButton component too? Remember the first arguement of mockery should be the exact string you try and require, not a relative path to the file

miracle2k commented 8 years ago

I didn't want to mock all image paths manually, so I made this hook:

var m = require('module');
var originalLoader = m._load;

m._load = function hookedLoader(request, parent, isMain) {
  var file = m._resolveFilename(request, parent);
  if (file.match(/.jpeg|.jpg|.png$/))
    return {uri: file};

  return originalLoader(request, parent, isMain);
}
RealOrangeOne commented 8 years ago

@miracle2k oohh that's really nice! Could you submit that as a PR? Maybe putting it in mock.js?

sibelius commented 8 years ago

@miracle2k how can I use this hook with react-native-mock?

sibelius commented 8 years ago

hey @slamus can u provide a repo with your solution?

sibelius commented 8 years ago

@RealOrangeOne I think we should create a gitter room to discuss solutions for these issues

RealOrangeOne commented 8 years ago

I was thinking about doing something like that, let me look into it further before I create something

sibelius commented 8 years ago

for react-native-router-flux to run I've used this commands with Mockery on testHelper.js

import mockery from 'mockery';

mockery.enable();

mockery.registerMock('./menu_burger.png', 'burguer');
mockery.registerMock('./back_chevron.png', 'chevron');
varmais commented 8 years ago

I'm running my tests with mocha and found mocha-image-compiler useful when testing components which require images. See my example repository for examples: react-native-unit-tests.

It comes with downside though: You will get a prop type warning when running the tests, but I'll rather take the warning than leave components untested.

RealOrangeOne commented 8 years ago

@varmais Wow, that's really nice! Although yes the prop warning would get annoying. It's definately much easier than using mockery! We could fork that library and add it in, conditionally obviously.

zhaotai commented 8 years ago

Well, the solution of @miracle2k can not solve my problem directly because the image name they mock is just the same to the actual image name.

But as we all known, if you require an image without 2x or 3x postfix just like abc.png instead of abc@2x.png, react-native will also require the right one automatically depending on the active device. And that's why I confronted this problem and all above didn't solve it.

So inspired by the code of @miracle2k , I replaced the file with request directly because in my situation there is no abc.png at all and the function _resolveFilename will throw a can not find module error. At last, my code is below

const m = require('module');
const originalLoader = m._load;

m._load = function hookedLoader(request, parent, isMain) {
  if (request.match(/.jpeg|.jpg|.png$/)) {
    return { uri: request };
  }

  return originalLoader(request, parent, isMain);
};

I haven't tried the mockery solution because I use mocha and enzyme as my unit test library and I have a setup.js file to handle all the mocks whatever the native modules or third-party modules. And the best solution for me is to write these code in the same file.

ryyppy commented 8 years ago

Funny that this discussion just arised.. I was working on the same mocking problem independently yesterday and came to a very similar (non-working) solution as @zhaotai ... this will save me many hours of debugging the node module system... it's also not as intrusive as mockery.

I know mockery and I used it for other products myself.. the thing is, before I would ever use it, I might as well just use jest instead ... because jest does practically the exact same thing more efficiently and with first level React-Native support...

GantMan commented 8 years ago

Just tested the @zhaotai and @miracle2k solution. Works fantastic. Going to be moving this into our default AVA tests for the Ignite project. Now everyone who creates a new Ignite starter will have their work right away. I'll be sure to make more shout outs in the release notes. Thanks!

GantMan commented 8 years ago

O and I"m closing this ticket :) cheers!

icynoangel commented 8 years ago

Hmm... I have added the hookLoader for images in my mocha setup file, I don't get the error about images anymore, but I do have the following error:

TypeError: Plugin 1 specified in "/node_modules/react-native-router-flux/node_modules/react-native-experimental-navigation/package.json" was expected to return a function but returned "undefined"

Anyone got this error?