bartonhammond / snowflake

:snowflake: A React-Native Android iOS Starter App/ BoilerPlate / Example with Redux, RN Router, & Jest with the Snowflake Hapi Server running locally or on RedHat OpenShift for the backend, or a Parse Server running locally or remotely on Heroku
http://bartonhammond.github.io/snowflake/snowflake.js.html
MIT License
4.59k stars 614 forks source link

[QUESTION] Can't properly setup async actions test on my app #48

Closed chukcha-wtf closed 8 years ago

chukcha-wtf commented 8 years ago

Hi, I was looking at your project to get basic ideas for testing my RN app with redux, redux-thunk and a set of async actions. Thanks for your great work I've been able to successfully test basic actions and components rendering, but can't make it work with promises.

My app is RN 0.18 application with the following package.json config:

{
  "name": "myApp",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "react-native start",
    "test": "rm -rf ./node_modules/jest-cli/.haste_cache && jest --verbose",
    "test:watch": "npm run test -- --watch"
  },
  "jest": {
    "scriptPreprocessor": "jestSupport/scriptPreprocess.js",
    "setupEnvScriptFile": "node_modules/react-native/jestSupport/env.js",
    "testPathIgnorePatterns": [
      "/node_modules/",
      "packager/react-packager/src/Activity/"
    ],
    "testFileExtensions": [
      "js"
    ],
    "moduleFileExtensions": [
      "js"
    ],
    "unmockedModulePathPatterns": [
      "react",
      "react-addons-test-utils",
      "promise",
      "source-map",
      "key-mirror",
      "immutable",
      "fetch",
      "redux",
      "redux-thunk",
      "fbjs"
    ]
  },
  "dependencies": {
    "lodash": "^3.10.1",
    "react-native": "0.18.0",
    "react-native-android-statusbar": "^0.1.2",
    "react-native-fs": "1.1.0",
    "react-native-sqlite-storage": "^2.1.1",
    "react-native-vector-icons": "^1.0.0",
    "react-redux": "3.1.2",
    "react-timer-mixin": "^0.13.3",
    "redux": "^3.0.5",
    "redux-thunk": "^1.0.2"
  },
  "devDependencies": {
    "art": "^0.10.1",
    "babel-core": "^6.4.5",
    "babel-jest": "^6.0.1",
    "d3": "^3.5.12",
    "jest-cli": "^0.8.2",
    "moment": "^2.10.6",
    "react": "^0.14.3",
    "react-addons-test-utils": "^0.14.3"
  }
}

My tests are placed at the top directory like so:

. myApp
|-- index.android.js
|-- index.ios.js
|-- app/
|-- android/
|-- ios/
|-- __tests__
|  |-- actions
|    └-- appState-test.js
|-- mocks
|  └-- Store.js
└-- package.json

Store.js file looks exactly like the one you've got here and my appState-test.js file looks like:

'use strict';

jest.autoMockOff();

const mockStore = require('../../exampleStore/snowflakeStore').default;
const actions = require('../../app/actions/appState');

import * as types from '../../app/constants/actionTypes';

describe('appStateSnowflake', () => {
  pit('should fetchSMTH', () => {    
    const expectedActions = [
      {type: types.START_FETCH_SMTH},
      {type: types.END_FETCH_SMTH}
    ];

    const store = mockStore({}, expectedActions);
    return store.dispatch(actions.fetchSMTH());
  });
});

Where actions/appState.js looks following:

import * as types from '../constants/actionTypes';

function startFetchSMTH() {
  console.log('startFetchSMTH')
  return { type: types.START_FETCH_SMTH }
}

function endFetchSMTH() {
  console.log('endFetchSMTH')
  return { type: types.END_FETCH_SMTH }
}

function doSomething() {
  console.log('Doing smth')
  return Promise.resolve();
}

export function fetchSMTH() {
  console.log('fetchSMTH')

  return (dispatch) => {
    dispatch(startFetchSMTH());

    return doSomething().then(() => {
      console.log('Im here HELP ME!!!')
      dispatch(endFetchSMTH())
    });
  }
}

However when I'm running my tests, only START_FETCH_SMTH has been dispatched and then I'm getting timeout: timed out after 5000 msec waiting for something to happen. As for me this config looks exactly like the one in snowflake (tests from snowflake are running well) but they still doesn't work and I can't figure out why. Will appreciate if someone can help me as there isn't much information about RN testing, especially with Jest.

If this is an inappropriate question/issue - feel free to close it.

Thanks.

bartonhammond commented 8 years ago

Glad you like the project!

I'm not sure you're getting the mocked store

const mockStore = require('../../exampleStore/snowflakeStore').default;

I point this out because your directory layout shows something different with mocks/store.js

You should console.log(mockStore) in the test to confirm mockStore is the mocked version.

You could also npm run test-chrome after putting a break point in the mockStore to confirm it's getting picked up by jest.

Otherwise, nothing jumps out at me.

Let me know what happens

chukcha-wtf commented 8 years ago

Thanks for the response. Actually, I'm getting a store, it's just an object with getState() and dispatch() functions (I can see it in my console.log and can get console.logs from inside that store, it was just a typo in file path as I can't share the actual source code) but it still don't want to work with promises. I've also tried this setup from official redux documentation, they're using it instead of pit with some done callback, it also doesn't work as expected (nothing gets dispatched), however tests are marked as successfull - but that's not a solution.

Can you tell how'd you come to such store mock implementation? It's a bit different from redux example and I'm interested why are you using pit instead of it with callback?

bartonhammond commented 8 years ago

pit is for a promised test. To understand it better, you should try changing the pit to it and see what happens on the stock Snowflake repo.

Jest is built on top of Jasmine and the Jest API docs reference PIT with this: https://www.npmjs.com/package/jasmine-pit.

When writing my tests I had problems w/ the redux version of the store and so I modified it to get my tests working. I also add try/catch so I could have some idea of what failed.

chukcha-wtf commented 8 years ago

Thank you, I've gone through jasmine documentation, and that's why I've asked, as redux test example is using it to test promises which is a bit strange for me. In my case, all actions are working well when running within the app itself, but when testing - everything stuck on doSomething() function.

larubbio commented 8 years ago

I'm running into the same issue. Something in the upgrade from react-native 0.17.1 to 0.18 is causing these tests to fail. If you take the base snowflake repo and update the versions the tests will fail. Here are the changed packages:

react-native@0.17.0 --> react-native@0.18.1
fbjs-haste@0.3.4    --> <not installed>
react-haste@0.14.2  --> <not installed>
                    --> │ ├─┬ react-transform-hmr@1.0.1
                        │ │ ├─┬ global@4.3.0
                        │ │ │ ├─┬ min-document@2.18.0
                        │ │ │ │ └── dom-walk@0.1.1
                        │ │ │ └── process@0.5.2
                        │ │ └─┬ react-proxy@1.1.2
                        │ │   └── react-deep-force-update@1.0.1
react-redux@3.1.2   --> react-redux@4.1.1

I also noticed that the current implementation of the store should also check that all of the expectedActions are emitted. That is why changing from pit to it gets the test passing. The store receives the first action but not the second.

bartonhammond commented 8 years ago

Seems like every time RN upgrades they break all the Jest testing...

larubbio commented 8 years ago

Well RN 0.19.0-rc1 is out (but the tests are still broken with it :) )

bartonhammond commented 8 years ago

Snowflake was just fixed 6 days ago for RN 17! And I didn't figure it out - some one else did. This is frustrating.

larubbio commented 8 years ago

How do you set a breakpoint when testing? if I run npm run test-chrome it launches chrome and stops at the first line in jest.js but none of my code is loaded in chrome dev tools so I can't set any breakpoints. If I continue code starts to load but the test run finishes and the debugger disconnects.

Nevermind, setting --preload true in package.json let me set the breakpoint, but since the code was transpiled it's unreadable so the debugger seems useless.

bartonhammond commented 8 years ago

Set a break point in your source code before running npm run test-chrome

bartonhammond commented 8 years ago

@larubbio What steps do you use do to upgrade? I did npm update and RN did not change version

larubbio commented 8 years ago

For me I manually upgraded the versions in packages.json to the latest versions. I also had to move to npm3 because of an issue with fbjs (https://github.com/facebook/react-native/issues/2985#issuecomment-168337176)

I then had to make some other small changes mostly the way redux was required since the /native entrypoint was dropped.

bartonhammond commented 8 years ago

@larubbio I just removed all the package.json dependancies and dev-dependencies and ran command line for every required module.

My tests run fine now w/ RN 18.1

My dependencies:

  "dependencies": {
    "apsl-react-native-button": "git+https://git@github.com/bartonhammond/react-native-button.git",
    "immutable": "^3.7.6",
    "key-mirror": "^1.0.1",
    "react-native": "^0.18.1",
    "react-native-gifted-spinner": "0.0.3",
    "react-native-simple-store": "^0.1.0",
    "react-native-simpledialog-android": "^1.0.2",
    "react-native-tab-navigator": "^0.2.15",
    "react-native-vector-icons": "^1.1.0",
    "react-redux": "^4.1.1",
    "redux": "^3.1.2",
    "redux-thunk": "^1.0.3",
    "regenerator": "^0.8.42",
    "tcomb-form-native": "^0.3.3",
    "underscore": "^1.8.3",
    "validate.js": "^0.9.0"
  },
  "devDependencies": {
    "babel-core": "^6.4.5",
    "babel-jest": "^6.0.1",
    "docker": "^0.2.14",
    "istanbul": "^0.4.2",
    "jest-cli": "^0.8.2",
    "react": "^0.14.7",
    "react-addons-test-utils": "^0.14.7"
  }

Now to a pit test

I changed authActions-test.js to just this:

describe('authActions', () => {

  pit('should logout', () => {
    const expectedActions = [
      {type: LOGOUT_REQUEST},
      {type: LOGIN_STATE_REGISTER},
      {type: LOGOUT_SUCCESS},
      {type: SESSION_TOKEN_REQUEST},
      {type: SESSION_TOKEN_SUCCESS}
    ];
    const store = mockStore({}, expectedActions);
    return store.dispatch(actions.logout());
  });
});

Then I run npm test src/reducers/auth/__tests__/authActions-test.js and get:

 PASS  src/reducers/auth/__tests__/authActions-test.js (0.839s)
authActions
  ✓ it should logout
1 test passed (1 total in 1 test suite, run time 2.522s)

Then, change the pit it runs too.

But now try this - notice the it and missing actions

  it('should logout', () => {
    const expectedActions = [
      {type: LOGOUT_REQUEST}
    ];
    const store = mockStore({}, expectedActions);
    return store.dispatch(actions.logout());
  });

That runs as:

 PASS  src/reducers/auth/__tests__/authActions-test.js (0.765s)
authActions
  ✓ it should logout
1 test passed (1 total in 1 test suite, run time 2.386s)

But that's not right as there are other actions that ran!

So change that it back to pit and rerun

  pit('should logout', () => {
    const expectedActions = [
      {type: LOGOUT_REQUEST}

    ];

    const store = mockStore({}, expectedActions);
    return store.dispatch(actions.logout());
  });

That fails, as it should!

Store.action Object { type: 'LOGIN_STATE_LOGIN' }
 FAIL  src/reducers/auth/__tests__/authActions-test.js (0.819s)
authActions
  ✕ it should logout

 FAIL  src/reducers/auth/__tests__/authActions-test.js (0.819s)
● authActions › it should logout
  - Error: TypeError: Cannot read property 'type' of undefined
        at dispatch (src/reducers/mocks/Store.js:52:7)
        at eval (node_modules/redux-thunk/lib/index.js:9:61)
        at dispatch (node_modules/redux/lib/applyMiddleware.js:46:8)
        at eval (src/reducers/auth/authActions.js:354:2148)
        at tryCallOne (node_modules/react-native/node_modules/promise/lib/core.js:37:8)
        at eval (node_modules/react-native/node_modules/promise/lib/core.js:123:9)
        at flush (node_modules/react-native/node_modules/promise/node_modules/asap/raw.js:50:21)
        at doNTCallback0 (node.js:419:9)
        at process._tickCallback (node.js:348:13)
1 test failed, 0 tests passed (1 total in 1 test suite, run time 2.42s)

The mock store can tell you when actions aren't run when they were expected to, but not tell you when more actions were run then expected actions.

bartonhammond commented 8 years ago

I get the following when running Xcode:

rror building DependencyGraph:
 Error: Naming collision detected: /Users/barton/projects/stargazers/temp/snowflake/node_modules/react-native/node_modules/fbjs/lib/CSSCore.js collides with /Users/barton/projects/stargazers/temp/snowflake/node_modules/react/node_modules/fbjs/lib/CSSCore.js
    at HasteMap._updateHasteMap (HasteMap.js:132:13)
    at HasteMap.js:103:28
    at tryCallOne (/Users/barton/projects/stargazers/temp/snowflake/node_modules/react-native/node_modules/promise/lib/core.js:37:12)
    at /Users/barton/projects/stargazers/temp/snowflake/node_modules/react-native/node_modules/promise/lib/core.js:123:15
    at flush (/Users/barton/projects/stargazers/temp/snowflake/node_modules/react-native/node_modules/promise/node_modules/asap/raw.js:50:29)
    at doNTCallback0 (node.js:419:9)
    at process._tickCallback (node.js:348:13)
Fri, 29 Jan 2016 00:16:32 GMT ReactNativePackager:SocketServer exit code: 1

    at terminate (SocketClient.js:59:17)
    at Socket.<anonymous> (SocketClient.js:74:37)
    at emitOne (events.js:77:13)
    at Socket.emit (events.js:169:7)
    at emitErrorNT (net.js:1253:8)
    at doNTCallback2 (node.js:441:9)
    at process._tickCallback (node.js:355:17)
Command /bin/sh failed with exit code 1
larubbio commented 8 years ago

That error is why I had to move to npm3. It installs dependencies flat which removes the conflict. I think you might also be able to resolve it by directly installing fbjs.

larubbio commented 8 years ago

I updated my versions to match yours but I still get the timeout.

I noticed the mock store will tell you if actions are raised that match the start of expectedActions, but not error out if more or fewer actions are raised. I was going to look at fixing that once I got my tests passing.

bartonhammond commented 8 years ago

That's not correct @larubbio - I documented that above quite clearly.

bartonhammond commented 8 years ago

@larubbio I don't know anything about npm3 - has ReactNative required this now?

larubbio commented 8 years ago

Are you sure about that? Looking at the code it's not clear to me how it would error if there were more actions than expected. It looks like the store just compares the list in order against the actions that are raised, but never checks that the count of raised actions matches the count of expected.

larubbio commented 8 years ago

I just meant version 3 of npm. RN doesn't require it, but the way it installs dependencies resolved the fbjs issue for me.

larubbio commented 8 years ago

@bartonhammond When I say "errors" I mean it doesn't fail the test when it should. It doesn't crash.

larubbio commented 8 years ago

@bartonhammond In a version still using RN 0.17.1 I changed the test to this:

  pit('should logout', () => {
    const expectedActions = [
      {type: LOGOUT_REQUEST},
      {type: LOGIN_STATE_REGISTER},
      {type: LOGOUT_SUCCESS},
      {type: SESSION_TOKEN_REQUEST},
      {type: SESSION_TOKEN_SUCCESS},
      {type: "GOODBYE"}
    ];

    const store = mockStore({}, expectedActions);
    return store.dispatch(actions.logout());
  });

and it passes even though the event ' {type: "GOODBYE"}' is never raised. The test should have failed.

bartonhammond commented 8 years ago

@larubbio that is right - the test should have failed. but if you moved that GOODBYE somewhere else in the sequence, it would fail

bartonhammond commented 8 years ago

Are you sure about that? Looking at the code it's not clear to me how it would error if there were more actions than expected. It looks like the store just compares the list in order against the actions that are raised, but never checks that the count of raised actions matches the count of expected.

It can tell when there are missing, but not when there are more, if the extra are appended to the original set

bartonhammond commented 8 years ago

@larubbio

I was going to look at fixing that once I got my tests passing.

That would be much appreciated. I've created a different issue, #52 to upgrade to RN 0.18 as separate issue.

My apologies if I confused anything - this has been a long long long day... ;)

bartonhammond commented 8 years ago

I upgraded today to RN 0.18.1 Please read the release notes. I changed the pit to it, not sure I understand... Also upgrade to npm3 Thanks for all the discussion and help.

bartonhammond commented 8 years ago

Most all tests are passing now, see release notes: https://github.com/bartonhammond/snowflake/releases/tag/0.1.2-alpha