bartonhammond/snowflake

How to test 'ErrorAlert' which is using native 'Alert' ?

Closed this issue · 12 comments

Hello,

This is not an issue, but rather a question.
I would like to test (thanks to Jest) my 'ErrorAlert' component, which is exactly the same as snowflake except that it uses the native 'Alert' rather than 'SimpleAlert'.

I tried to test it the same way @bartonhammond does but no results, do you guys have any ideas ?

Here is my ErrorAlert :

'use strict';

import {Alert} from 'react-native';
import  _ from 'underscore';

var ErrorAlert = class ErrorAlertClass{

  checkError(obj) {
    let errorMessage = '';

    if (!_.isNull(obj) && !_.isUndefined(obj)) {
      if (!_.isUndefined(obj.error)) {
        if (!_.isUndefined(obj.error.error)) {
          errorMessage = obj.error.error;
        } else {
          errorMessage = obj.error;
        }
      } else {
        errorMessage = obj;
      }
      if (errorMessage !== '') {
        if (!_.isUndefined(errorMessage.message)) {
          Alert.alert("Une erreur s'est produite",errorMessage.message);
        } else {
          Alert.alert("Une erreur s'est produite",errorMessage);
        }
      }
    }
  }
};

module.exports = ErrorAlert;

And my actual test (I tried several tests) :

'use strict';

import 'react-native';
import React from 'react';

var Alert = require('react-native');
Alert.alert = jest.genMockFunction();
var ErrorAlert = require('../ErrorAlert');

describe('ErrorAlert', () => {

  it('should be fine', () => {
    const errorAlertProps = {
      error: {
        error: 'Error occurred'
      }
    };
    new ErrorAlert().checkError(errorAlertProps);

    expect(Alert.alert.mock.calls[0][0]).toEqual("Error");
    expect(Alert.alert.mock.calls[0][1]).toEqual(errorAlertProps.error.error);
  });
});

I know I'm missing something... :/

Note how I am mocking out the react-native-simpledialog-android object. See src/__mocks__/react-native-simpledialog-android.js.

I'm not sure if defining the mock w/in the test class works. You might want to dump that object to the console.log to confirm you are getting that Alert as a mock.

Please provide the output from the test run npm run test src/components/__tests__/ErrorAlert-test.js

Thank you for your fast answer.
Here is my "Alert" log :

 { ActivityIndicator: [Getter],
   [...]
  PointPropType: [Getter],
  addons:
   { LinkedStateMixin: [Getter],
     Perf: [Getter],
     PureRenderMixin: [Getter],
     TestModule: [Getter],
     TestUtils: [Getter],
     batchedUpdates: [Getter],
     createFragment: [Getter],
     update: [Getter] },
  hasReactNativeInitialized: false,
  findNodeHandle: [Function: findNodeHandle],
  render: [Function],
  unmountComponentAtNode: [Function],
  unstable_batchedUpdates: [Function: batchedUpdates],
  unmountComponentAtNodeAndRemoveContainer: [Function],
  alert:
   { [Function]
     _isMockFunction: true,
     getMockImplementation: [Function],
     mock: { calls: [], instances: [] },
     mockClear: [Function],
     mockReturnValueOnce: [Function],
     mockReturnValue: [Function],
     mockImplementationOnce: [Function],
     mockImpl: [Function],
     mockImplementation: [Function],
     mockReturnThis: [Function] } }

I just tried your exact implementation (but with Alert) I've the exact same output as before, which is :

Using Jest CLI v14.1.0, jasmine2, babel-jest, jest-react-native preset
 FAIL  src\components\__tests__\ErrorAlert-test.js (5.003s)
 ErrorAlert › it should be fine
  - TypeError: Cannot read property '0' of undefined
        at Object.<anonymous> (src\components\__tests__\ErrorAlert-test.js:51:37)
  ErrorAlert
    ✕ it should be fine (33ms)

1 test failed, 0 tests passed (1 total in 1 test suite, run time 12.332s)
-----------------------|----------|----------|----------|----------|----------------|
File                   |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
-----------------------|----------|----------|----------|----------|----------------|
 components\           |    86.11 |    61.54 |      100 |    82.35 |                |
  ErrorAlert.js        |    86.11 |    61.54 |      100 |    82.35 |       37,40,44 |
 components\__tests__\ |    92.31 |       75 |      100 |    90.91 |                |
  ErrorAlert-test.js   |    92.31 |       75 |      100 |    90.91 |             52 |
-----------------------|----------|----------|----------|----------|----------------|
All files              |    87.76 |    63.33 |      100 |    85.71 |                |
-----------------------|----------|----------|----------|----------|----------------|

I'm a little lost. Thanks again for your precious help.

It seems like, indeed I have a mocked Alert but the call made by "ErrorAlert().checkError(errorAlertProps):", do not fill the mock.calls array - when I log it, it is just an empty array.

Note TypeError: Cannot read property '0' of undefined. I would update the test after this line new ErrorAlert().checkError(errorAlertProps); to dump the mocked object (e.g. console.log(Alert.alert))

You need to see what values are actually in that mocked object.

I agree, I already logged it (see the last two lines of my precedent post).
I'm not sure I understand what you mean by "update the test". How would you do that ?

Here is the logs (right after new ErrorAlert().checkError(errorAlertProps);) :

Alert.alert.mock : { calls: [], instances: [] }

Alert.alert : 
function () {
      instances.push(this);
      calls.push(Array.prototype.slice.call(arguments));
      if (this instanceof f) {
        // This is probably being called as a constructor
        prototypeSlots.forEach(slot => {
          // Copy prototype methods to the instance to make
          // it easier to interact with mock instance call and
          // return values
          if (prototype[slot].type === 'function') {
            const protoImpl = this[slot];
            this[slot] = generateFromMetadata(prototype[slot]);
            this[slot]._protoImpl = protoImpl;}});

        // Run the mock constructor implementation
        return mockImpl && mockImpl.apply(this, arguments);}

      let returnValue;
      // If return value is last set, either specific or default, i.e.
      // mockReturnValueOnce()/mockReturnValue() is called and no
      // mockImplementationOnce()/mockImplementation() is called after that.
      // use the set return value.
      if (isReturnValueLastSet) {
        returnValue = specificReturnValues.shift();
        if (returnValue === undefined) {
          returnValue = defaultReturnValue;}}

      // If mockImplementationOnce()/mockImplementation() is last set,
      // or specific return values are used up, use the mock implementation.
      let specificMockImpl;
      if (returnValue === undefined) {
        specificMockImpl = specificMockImpls.shift();
        if (specificMockImpl === undefined) {
          specificMockImpl = mockImpl;}

        if (specificMockImpl) {
          return specificMockImpl.apply(this, arguments);}}

      // Otherwise use prototype implementation
      if (returnValue === undefined && f._protoImpl) {
        return f._protoImpl.apply(this, arguments);}

      return returnValue;}

What I meant is to update the test like this, so that after the checkError, you dump the mock object

    new ErrorAlert().checkError(errorAlertProps);

    console.log(Alert.alert.mock.calls);  //<<<<<< add this

    //expect(Alert.alert.mock.calls[0][0]).toEqual("Error");
    //expect(Alert.alert.mock.calls[0][1]).toEqual(errorAlertProps.error.error);

Alright then, that is exactly what I've done.
The Alert.alert.mock.calls array is empty but I don't understand why. :|

One thing I'm concerned about, I don't like that the mock object is defined w/in the __test__ class. I'm not sure that the ErrorAlert class will pick up that mock. Try moving the implementation to src/__mocks__/Alert.js similar to src/__mocks__/react-native-simpledialog-android.js.

See if that makes a difference. I suspect the test class knows about the mock but the ErrorAlert class, which is being test, does not.

I think having the mock defined in the location that Jest defines is the correct design pattern and I've had very good success with it.

Since Alert is a node_module, the proper way, or recommended way, to mock it is put it in the top level directory so that all references to it will pick it up automatically. That's why the react-native-simpledialog-android.js is declared there.

If moving the mock class to src/__mocks__ doesn't provide any insight, then I would suspect the implementation code of the ErrorAlert class.

Try putting a debugger statement in your ErrorAlert class and, from the snowflake terminal, enter npm run test-chrome src/components/__tests__/ErrorAlert-test.js and try debugging it.

Thank you for all your time and effort. I've already tried to move the implementation to src/__mocks__/Alert.js and implement it exactly the same way as you do but without success. In fact I obtain the exact same result w/ your implementation or mine.

I will try to debug ErrorAlert monday and edit this post with fresh news.
Thank you again.

I would recommend staying with the mocks location for consistency.
Good luck...let me know how it goes.

On Aug 26, 2016 12:45 PM, "Brokray" notifications@github.com wrote:

Thank you for all your time and effort. I've already tried to move the
implementation to src/mocks/Alert.js and implement it exactly the
same way as you do but without success. In fact I obtain the exact same
result w/ your implementation or mine.

I will try to debug ErrorAlert monday and edit this post with fresh news.
Thank you again.


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#131 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABORPA7-cPLltHwqj1WbjRup06Vtr8iBks5qjyZMgaJpZM4JtCnO
.

@Brokray , any update on this?

@bartonhammond Not really, I didn't find anything to debug in my ErrorAlert.js.
At the moment I put this aside because I've other tasks to fulfil.
I will continue this a bit later and let you know how it goes. Thanks again for all your help.

I think I've answered this as best I can and have nothing more to contribute.