swpp201901-team06 / swpp-project

1 stars 2 forks source link

Testing sagas #69

Closed lionminhu closed 5 years ago

lionminhu commented 5 years ago

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 (https://github.com/diegohaz/arc/issues/131#issuecomment-314497638).

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 commented 5 years ago

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 (https://github.com/diegohaz/arc/pull/101#issuecomment-280082146).

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

lionminhu commented 5 years ago

From https://github.com/facebook/create-react-app/issues/517#issuecomment-243488178

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 commented 5 years ago

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

lionminhu commented 5 years ago

그리고 애초에 위키에서 말하는 테스팅은 컴포넌트들을 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 commented 5 years ago

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 commented 5 years ago

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

lionminhu commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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 (https://github.com/swpp201901-team06/swpp-project/issues/69#issuecomment-502202772). 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 commented 5 years ago

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 commented 5 years ago

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