adamgruber / mochawesome

A Gorgeous HTML/CSS Reporter for Mocha.js
https://gitter.im/mochawesome/general
MIT License
1.06k stars 160 forks source link

Code does not appear when async/await is used with test #244

Open prescottprue opened 6 years ago

prescottprue commented 6 years ago

When using async/await in tests, the code for the test appears as just ; (both JSON and HTML report). Switching the same test logic back to a normal function (non async) things start working again:

function longPromise() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({})
    }, 1000);
  });
}

describe('Async tests', () => {
  // Works as expected
  it('should return code when returning a promise', () => {
    return longPromise().then(result => {
      expect(result).to.exist;
    });
  });

  // Works as expected
  it('should return code when using done', (done) => {
    longPromise().then(result => {
      expect(result).to.exist;
      done();
    });
  });

  // DOES NOT WORK - code is ";"
  it('should return code when using async/await', async () => {
    const result = await longPromise();
    expect(result).to.exist;
  });
});

Running the above example yields JSON results that look like so:

      {
        "uuid": "e265b085-5692-4801-b2f8-a8f7a87e0c72",
        "title": "Async tests",
        "fullFile": "<- file path ->",
        "file": "<- file path ->",
        "beforeHooks": [],
        "afterHooks": [],
        "tests": [
          {
            "title": "should return code when returning a promise",
            "fullTitle": "Async tests should return code when returning a promise",
            "timedOut": false,
            "duration": 2007,
            "state": "passed",
            "speed": "slow",
            "pass": true,
            "fail": false,
            "pending": false,
            "code": "return longPromise().then(function (result) {\n  expect(result).to.exist;\n});",
            "err": {},
            "isRoot": false,
            "uuid": "41825a27-cd6f-4b65-b261-c7e606e17581",
            "isHook": false,
            "skipped": false
          },
          {
            "title": "should return code when using done",
            "fullTitle": "Async tests should return code when using done",
            "timedOut": false,
            "duration": 2003,
            "state": "passed",
            "speed": "slow",
            "pass": true,
            "fail": false,
            "pending": false,
            "code": "longPromise().then(function (result) {\n  expect(result).to.exist;\n  done();\n});",
            "err": {},
            "isRoot": false,
            "uuid": "0c6854b6-ca36-4bdd-b7a2-3882232f5234",
            "isHook": false,
            "skipped": false
          },
          {
            "title": "should return code when using async/await",
            "fullTitle": "Async tests should return code when using async/await",
            "timedOut": false,
            "duration": 2004,
            "state": "passed",
            "speed": "slow",
            "pass": true,
            "fail": false,
            "pending": false,
            "code": ";",
            "err": {},
            "isRoot": false,
            "uuid": "e268da13-766c-45a8-8d58-08ca2b3dd261",
            "isHook": false,
            "skipped": false
          }
        ],
        "suites": [],
        "passes": [
          "41825a27-cd6f-4b65-b261-c7e606e17581",
          "0c6854b6-ca36-4bdd-b7a2-3882232f5234",
          "e268da13-766c-45a8-8d58-08ca2b3dd261"
        ],
        "failures": [],
        "pending": [],
        "skipped": [],
        "duration": 6014,
        "root": false,
        "rootEmpty": false,
        "_timeout": 300000
      },

It is expected that the code would also show for the third test even though it uses async/await.

My guess it that it might have something to do with how the parsing of the babelified code is happening. I am using babel, so I believe the async/await code will be converted to generators, but not sure where the code drop is happening. Transpilation can be disabled, but not sure that would solve anything.

Let me know if more info would be helpful.

adamgruber commented 6 years ago

An example repo with your babel setup would help. Also, what version of mochawesome?

prescottprue commented 6 years ago

Using the latest (3.0.2) and my babelrc file looks like so:

{
  "presets": ["env"],
  "plugins": [
    ["module-resolver", {
     "root": ["./"]
   }]
  ]
}

It is using babel-preset-env and babel-plugin-module-resolver. If you want to see the debug output, provide debug: true to preset env (i.e. ["env", { "debug": true }]). My debug output looks like so:

Using plugins:
  check-es2015-constants {}
  transform-es2015-arrow-functions {}
  transform-es2015-block-scoped-functions {}
  transform-es2015-block-scoping {}
  transform-es2015-classes {}
  transform-es2015-computed-properties {}
  transform-es2015-destructuring {}
  transform-es2015-duplicate-keys {}
  transform-es2015-for-of {}
  transform-es2015-function-name {}
  transform-es2015-literals {}
  transform-es2015-object-super {}
  transform-es2015-parameters {}
  transform-es2015-shorthand-properties {}
  transform-es2015-spread {}
  transform-es2015-sticky-regex {}
  transform-es2015-template-literals {}
  transform-es2015-typeof-symbol {}
  transform-es2015-unicode-regex {}
  transform-regenerator {}
  transform-exponentiation-operator {}
  transform-async-to-generator {}
  syntax-trailing-function-commas {}

I believe it is the async-to-generator transform that is responsible.

adamgruber commented 6 years ago

@prescottprue Thanks for the extra info but in order to replicate your exact setup it would be helpful to have some more. What version of node, mocha, babel, etc? How are you running tests? What options are you passing to mocha?

It's difficult for me to put together an accurate repro if I have to guess at some of these things. Best thing would be a link to a repo I can just clone.

prescottprue commented 6 years ago

@adamgruber Totally understandable, I'll make a small example later today and get back to you.

Didn't provide the full repro initially since I was seeing the same in multiple projects regardless of what node version (6.11.5, 6.14.0, 8.6.0, 9.11.1, and 10.4.1) and with multiple versions of mocha. Was hoping it was just a small "add this preset" or something 😆 .

Side note: Great work with mochawesome!🥇 Haven't bumped into any issues yet, and this seems like the async/await stuff may be a small fix or just a mixup in my babel config.

prescottprue commented 6 years ago

@adamgruber Here is a simple example. It uses the mocha.opts file to require babel.

I am seeing the code not appear for the final test in that example regardless of node version (6.11.5, 6.14.0, 9.11.1 and 10.4.1).

Something else I noticed is that the babelified code is what is appearing instead of the original code - makes sense since that is what is being run, but might be what is causing this if whatever is selecting the code doesn't handle the async. If that were the case, I assume that would be on the mocha side and not in the awesomereporter, but not sure.

adamgruber commented 6 years ago

@prescottprue Thanks for the sample code. Its a HUGE help.

Here's what's going on. To display your test code in the report, mochawesome attempts to clean it by stripping off the leading function () {. It does this via a RegExp. In the case where you're only seeing ; as the output, this it the input:

function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }

The problem is that the RegExp expects a new line after the opening bracket but the input code is all one line so the RegExp is matching the entire line and replacing it with an empty string. I should be able to update the RegExp to handle this case, though it may take some time to ensure I don't introduce any regressions. It's a gnarly RegExp that tries to handle every possible function type. I've been considering rewriting the clean code utility to use an AST instead.

EDIT: I'm wrong about the new line expectation. It's something else in the line of code that is causing the RegExp to fail. AST is really the way to go here to cover all cases. The RegExp is only going to get more complex and brittle.

adamgruber commented 6 years ago

Should work now in 3.0.3

stale[bot] commented 5 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.