zmxv / react-native-sound

React Native module for playing sound clips
MIT License
2.79k stars 748 forks source link

Cannot get variable in callback in tests with jest #621

Open Under-Warz opened 4 years ago

Under-Warz commented 4 years ago

Hi, I'm trying to mock react-native-sound and make my component testable.

I'm able to mock the library with a custom mock, but I cannot get my sound variable available in the callback after init a new Sound

He is my simple mock (still WIP)

class SoundMocked {
  static setCategory = jest.fn()

  getDuration = jest.fn()

  constructor(filename, basePath, onError) {
    onError()
  }
}

export default SoundMocked

And here is my component using react-native-sound

...
useEffect(() => {
    const sound = new Sound(fileUrl, "", error => {
      if (error) {
        return
      }

      setSound(sound)
      setTotalDuration(sound.getDuration())
      clearDurationInterval()
    })
  }, [fileUrl])
...

When I'm using it in DEV or PROD, everything is correct, the sound variable is defined and I can get the duration.

But during the test with jest, the variable sound is undefined only in the callback block. If I console log the same variable outside the callback, I can see my mock variable

Any ideas?

schumannd commented 4 years ago

@Under-Warz Can you document precisely how you managed to mock this package? I have been unsuccessful in mocking it so far. How do you import the package, and what does your test look like? Where is your mock code placed?

Edit:

I managed to mock it successfully by.

A) using import Sound from 'react-native-sound'; instead of the require method.

B) creating a file __mocks__/react-native-sound.js with the content:

export default class SoundMocked {
  static setCategory = () => {};
  ... // whatever else you want to mock from this library
}
Megatron4537 commented 2 years ago

For my use case I did

jest.mock('react-native-sound', () => {
  class SoundMocked {
    static setCategory = jest.fn();
  }
  // SoundMocked.prototype.getDuation =jest.fn();
  return SoundMocked;
});
viniciushfrtc commented 2 years ago

You can lazy the callback a little, in my case I did

"__mocks__/react-native-sound.js":

class SoundMock {
  constructor(filename, basePath, onError, options) {
    setTimeout(() => onError(false), 10);
    return;
  }
}

SoundMock.prototype.isLoaded = true;
SoundMock.prototype.play = jest.fn();
SoundMock.prototype.setVolume = jest.fn();
SoundMock.prototype.setSpeed = jest.fn();
SoundMock.prototype.setNumberOfLoops = jest.fn();
SoundMock.prototype.stop = jest.fn();
SoundMock.prototype.getDuration = jest.fn(() => {
  return 60000;
});

SoundMock.setCategory = jest.fn();
SoundMock.setMode = jest.fn();

export default SoundMock;