Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing sagas #69

Closed
lionminhu opened this issue Jun 14, 2019 · 17 comments
Closed

Testing sagas #69

lionminhu opened this issue Jun 14, 2019 · 17 comments
Labels
bug Something isn't working Frontend

Comments

@lionminhu
Copy link

lionminhu commented Jun 14, 2019

I'm currently trying to create tests for sagas for MainPage, but when I import any saga function into the test file:

import { take, fork } from 'redux-saga/effects'
import { watchGotoSignIn, watchGotoGuest } from './sagas'
import * as actions from './actions'

describe('MainPage sagas test', () => {
  it('go to guest', async () => {
    const generator = await sagas.watchGoToGuest()
    expect(await generator.next().value).toEqual(take(actions.GOTO_GUEST))
    generator.next()
  })
})

running npm test results in an error:

 FAIL  src/store/MainPage/sagas.test.js
  ● Test suite failed to run

    TypeError: require.context is not a function
      
      at Object.<anonymous> (src/store/middlewares.js:1:121)
      at Object.<anonymous> (src/store/configure.js:4:20)
      at Object.<anonymous> (src/index.js:9:18)
      at Object.<anonymous> (src/store/MainPage/sagas.js:12:91)
      at Object.<anonymous> (src/store/MainPage/sagas.test.js:2:14)
      at handle (node_modules/worker-farm/lib/child/index.js:44:8)
      at process.<anonymous> (node_modules/worker-farm/lib/child/index.js:51:3)
      at process.emit (events.js:196:13)
      at emit (internal/child_process.js:860:12)
      at processTicksAndRejections (internal/process/task_queues.js:84:9)

This is a problem with ARc, since it doesn't mock sagas (diegohaz/arc#131 (comment)).

I'm trying to figure out if there is a way to mock sagas for testing. As a reference, I'm following this guide to build tests for sagas.

@lionminhu lionminhu added Frontend bug Something isn't working labels Jun 14, 2019
@lionminhu
Copy link
Author

lionminhu commented Jun 14, 2019

It's strange though; the wiki post from ARc seems to imply that importing the sagas and testing them should not pose any problem.

This is a problem with ARc, since it doesn't mock sagas

For clarification, refer to this comment (diegohaz/arc#101 (comment)).

Instead of mocking sagas, it might just be possible to mock the require.context entirely.

@lionminhu
Copy link
Author

lionminhu commented Jun 14, 2019

From facebook/create-react-app#517 (comment)

This should be solvable by a custom __mocks__ file for the index module that exports all of the modules OR creates a Proxy object for the index module that has getters during runtime.

I'll start looking into this possibility.

@theNocturni
Copy link
Contributor

ARc 위키에서 import하기 쉬울거라는 얘기는 없지 않나? 그리고 애초에 위키에서 말하는 테스팅은 컴포넌트들을 mock한 상태일테니 그다음에는 require.context 문제 없이 쉽게 되겠지

@lionminhu
Copy link
Author

그리고 애초에 위키에서 말하는 테스팅은 컴포넌트들을 mock한 상태일테니

The wiki post I linked to above was about sagas, not components. Specifically, it is about unit/integration-testing sagas. It doesn't explain how the sagas could be tested if they cannot even be imported.

ARc 위키에서 import하기 쉬울거라는 얘기는 없지 않나?

But yes, the wiki says nothing about "importing" the sagas, so it might just be that the tests and the sagas being tested are even in the same file.

@lionminhu
Copy link
Author

Installing babel-plugin-transform-require-context (docs) and running npm test results in the following error instead:

FAIL  src/store/MainPage/sagas.test.js
  ● Test suite failed to run

    Invariant Violation: _registerComponent(...): Target container is not a DOM element.
      
      at invariant (node_modules/fbjs/lib/invariant.js:42:15)
      at Object._renderNewRootComponent (node_modules/react-dom/lib/ReactMount.js:308:76)
      at Object._renderSubtreeIntoContainer (node_modules/react-dom/lib/ReactMount.js:399:32)
      at render (node_modules/react-dom/lib/ReactMount.js:420:23)
      at Object.<anonymous> (src/index.js:24:22)
      at Object.<anonymous> (src/store/MainPage/sagas.js:12:91)
      at Object.<anonymous> (src/store/MainPage/sagas.test.js:2:14)
      at handle (node_modules/worker-farm/lib/child/index.js:44:8)
      at process.<anonymous> (node_modules/worker-farm/lib/child/index.js:51:3)
      at process.emit (events.js:196:13)
      at emit (internal/child_process.js:860:12)
      at processTicksAndRejections (internal/process/task_queues.js:84:9)

@theNocturni
Copy link
Contributor

babel-plugin-transform-require-context 같은 플러그인들 보니까
require.context 자체를 바꾼다고 되어있어서 좀 꺼려지던데
모든 require.context에 대한 콜을 그 플러그인에서 지정한 함수로 가게 바꾸는 형식이라고

@lionminhu
Copy link
Author

lionminhu commented Jun 14, 2019

Defining a mock require.context for every file that uses require.context such as src/store/middlewares.js as below (according to this)

// This condition actually should detect if it's an Node environment
if (typeof require.context === 'undefined') {
  const fs = require('fs');
  const path = require('path');

  require.context = (base = '.', scanSubDirectories = false, regularExpression = /\.js$/) => {
    const files = {};

    function readDirectory(directory) {
      fs.readdirSync(directory).forEach((file) => {
        const fullPath = path.resolve(directory, file);

        if (fs.statSync(fullPath).isDirectory()) {
          if (scanSubDirectories) readDirectory(fullPath);

          return;
        }

        if (!regularExpression.test(fullPath)) return;

        files[fullPath] = true;
      });
    }

    readDirectory(path.resolve(__dirname, base));

    function Module(file) {
      return require(file);
    }

    Module.keys = () => Object.keys(files);

    return Module;
  };
}

results in the same error as above with npm test:

FAIL  src/store/MainPage/sagas.test.js
  ● Test suite failed to run

    Invariant Violation: _registerComponent(...): Target container is not a DOM element.
      
      at invariant (node_modules/fbjs/lib/invariant.js:42:15)
      at Object._renderNewRootComponent (node_modules/react-dom/lib/ReactMount.js:308:76)
      at Object._renderSubtreeIntoContainer (node_modules/react-dom/lib/ReactMount.js:399:32)
      at render (node_modules/react-dom/lib/ReactMount.js:420:23)
      at Object.<anonymous> (src/index.js:24:22)
      at Object.<anonymous> (src/store/MainPage/sagas.js:12:91)
      at Object.<anonymous> (src/store/MainPage/sagas.test.js:2:14)
      at handle (node_modules/worker-farm/lib/child/index.js:44:8)
      at process.<anonymous> (node_modules/worker-farm/lib/child/index.js:51:3)
      at process.emit (events.js:196:13)
      at emit (internal/child_process.js:860:12)
      at processTicksAndRejections (internal/process/task_queues.js:84:9)

And this nearly rules out the solution of mocking require.context. Another solution is to mock the sagas as first mentioned, or to modify the codes such that require.context is not used.

I'm more interested in the latter, as the former seems rather ARc-specific, and thus hard to search online.

@lionminhu
Copy link
Author

Just to confirm, I've also tried using babel-plugin-require-context-hook (according to this), but the same Invariant Violation error showed up as above.

@lionminhu
Copy link
Author

In src/store/sagas.js (link), the usage of require.context is as follows:

const req = require.context('.', true, /\.\/.+\/sagas\.js$/)
// ...
const sagas = []

req.keys().forEach((key) => {
  sagas.push(req(key).default)
})

export default function* () {
  yield sagas.map(fork)
}

Printing req.keys() gives us a list of strings:

(7) ["./ArchivePage/sagas.js", "./MainPage/sagas.js", "./PostPage/sagas.js", "./SearchPage/sagas.js", "./SideBar/sagas.js", "./SignInPage/sagas.js", "./SignUpPage/sagas.js"]
0: "./ArchivePage/sagas.js"
1: "./MainPage/sagas.js"
2: "./PostPage/sagas.js"
3: "./SearchPage/sagas.js"
4: "./SideBar/sagas.js"
5: "./SignInPage/sagas.js"
6: "./SignUpPage/sagas.js"
length: 7
__proto__: Array(0)

The only thing we need is the list of directories of sagas files. We may be able to implement this without using require.context.

@lionminhu
Copy link
Author

I want to try out the solution that uses fs.readdirSync (according to this Stack post), but even importing fs from src/store/sagas.js as import fs from 'fs' results in the following error:
img
img

@lionminhu
Copy link
Author

lionminhu commented Jun 14, 2019

None of the following changed the result:

I tried adding these by adding this near the end of the webpack.config.js (right before module.exports = config:

config = {
  ...config,
//  node: {
//    fs: 'empty',  
//  },
//  target: "async-node",
}

@lionminhu
Copy link
Author

The last comment was a mistake on my part; config was already declared with const, not with let or var.

Adding target: "async-node" results in the following error:
img

Adding node: {fs: 'empty' } makes the mentioned error message disappear.

However, when I added the following lines at the beginning of src/store/sagas.js

import fs from 'fs'
console.log(fs)

it outputs {}, indicating that the imported fs is an empty dictionary, probably because of node: {fs: 'empty' }.

@lionminhu
Copy link
Author

lionminhu commented Jun 15, 2019

Adding the following to the webpack.config.js (according to this post)

var fs = require('fs');

var nodeModules = {};
fs.readdirSync('node_modules')
  .filter(function(x) {
    return ['.bin'].indexOf(x) === -1;
  })
  .forEach(function(mod) {
    nodeModules[mod] = 'commonjs ' + mod;
  });

config = {
  ...config,
  target: 'node',
  externals: nodeModules,
}

results in the aforementioned error with Uncaught ReferenceError: require is not defined.

@lionminhu
Copy link
Author

Trying to dynamically import fs by setting the externals of config from webpack.config.js (according to this post)

config = {
  ...config,
  externals: {
    fs: 'require(\'fs\')',
  },
}

results in the following error:
img

@lionminhu
Copy link
Author

lionminhu commented Jun 15, 2019

At this point, I'm starting to believe that it might be a better idea to manually provide an array of paths for sagas files for req.keys (#69 (comment)). None of the solutions I've searched has solved this problem.

A better solution would be to study webpack and webpack configuration to figure out how require statements within externals attribute of module.exports for webpack.config.js works, but we're in a rush.

@lionminhu
Copy link
Author

Even hardcoding is difficult, since I don't know how I would replace the default attribute of req(key) below:

req.keys().forEach((key) => {
  sagas.push(req(key).default)
})

I've decided to give up on testing sagas. I'll have to modify ARc and study webpack to even hope for a clue, and our deadline is on Monday.

@lionminhu
Copy link
Author

Mocking fs might also be a possibility, but I'm not looking into that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working Frontend
Projects
None yet
Development

No branches or pull requests

2 participants