mochajs / mocha

☕️ simple, flexible, fun javascript test framework for node.js & the browser
https://mochajs.org
MIT License
22.6k stars 3.01k forks source link

Impossibility to change the description in "it (string, fn)" and "describe (string, fn)" during the execution of tests #3696

Closed iamnikas closed 5 years ago

iamnikas commented 5 years ago

Prerequisites

Description

Found a bug in the functions it () and describe () in which there is a description with variables does not change if variables change. I use Mocha to describe the end-points Rest API and the format of test descriptions I have follows a pattern: /METHOD URL?PAR_1=&PAR_2= ...

Example: /GET http://test.com/getAll?user_id=1234&resource=123

Each test and block it() uses different parameter values. Each test run is accompanied by the receipt of unique parameters in the before (root before hook) block. This is done so that the state and the launch of tests are independent of the previous ones. The set of parameters as it was said above was obtained from root before hook and written to globals in the environment array.

Code Example:

describe('[TEST]', function() {
    it('/GET http://test.com/getAll?user_id='+global.environment.user_id+'&resource='+global.environment.resource, function(done) {
        // ... 
        done();
    })
})

Steps to Reproduce

Expected behavior: I expect changes of values from global variables in the description of it() & describe(). The behavior of other variables (not global) may be the same as now.

Actual behavior: At the time of the tests I get the following spec line / GET http://test.com/getAll?user_id=undefined&resource=undefined. Variables in the description it(), describe() not updated

Reproduces how often: is always

Versions

iamnikas commented 5 years ago

@boneskull Hi Christopher! Can you tell me? How hard is it to fix my problem by expanding the functionality mocha?

boneskull commented 5 years ago

I don’t really understand. Can you please provide a repo or gist that shows the problem?

iamnikas commented 5 years ago

@boneskull Yes of course!)

https://github.com/iamnikas/mocha_title_bug

I prepared a project with a bug. It seems to me that the title of the description in the function it('title'+global.var, fn(){...}) & describe('title'+global.var, fn(){...}) should change if the variable in globals has been changed. To do this, I propose to slightly modify the --delay mode so that you can perform any logic before initializing the title descriptions in it() and describe().

Thus, such an order of execution will be possible:

  1. start node app
  2. load mocha modules
  3. init mocha with --delay
  4. any pesonal logic
  5. trigged run() mocha fn
  6. init test mocs, functions it(), describe()
  7. Running test process
plroebuck commented 5 years ago

This is not a bug.

Suite and test titles are normally set when the file is sourced, not during execution. If you want dynamic titles, you must use a function that returns the suite/test.

boneskull commented 5 years ago

thanks @plroebuck

plroebuck commented 5 years ago

See this SO article for example of dynamic generation.

iamnikas commented 5 years ago

See this SO article for example of dynamic generation.

@plroebuck And if there is no possibility to make tests dynamically?

plroebuck commented 5 years ago

Nikita, why would creating tests dynamically not be possible?

iamnikas commented 5 years ago

Nikita, why would creating tests dynamically not be possible?

The fact is that more than 3000 lines of test code have already been written, some of them have a multistage Chai nesting. Or is this not a problem?

plroebuck commented 5 years ago

From your code, doesn't this work?

describe('[RUN TESTS]', function() {
  function genGET() {
    it('[TEST] GET http://test.com?tick=' + global.environment.tick, function(done) {
      done();
    });
  }

  genGET();
});

Could move your root suite before inside your describe and use local variables rather than environment global variable.

let request = require('async-request'),      // <= Package was deprecated several years ago
    response;

describe('[RUN TESTS]', function() {
  var tick;

  before(async function() {
    try {
      console.log('[RUN TESTS] before hook');
      let response = await request('https://api.exmo.me/v1/ticker/');

      if (response.statusCode !== 200) {
        throw new Error('bad status code: ' + response.statusCode);
      }
      let response_body = JSON.parse(response.body);

      // random get item
      let max_items = Object.keys(response_body).length;
      let rnd_index = Math.floor(Math.random() * (max_items - 0)) + 0;

      //get item key and set the closure var
      tick = Object.keys(response_body)[rnd_index];
      genGET();    // <= Bad style but just a test here
    } catch(err) {
      console.error(err);
      this.skip();
    }
  });

  function genGET() {
    it('[TEST] GET http://test.com?tick=' + tick, function(done) {
      done();
    });
  }

  it('placeholder');
});
plroebuck commented 5 years ago

Nikita, why would creating tests dynamically not be possible?

The fact is that more than 3000 lines of test code have already been written, some of them have a multistage Chai nesting. Or is this not a problem?

You wrote 3000 lines of test code without running it at all? Sorry, but I'd have to see more than your simple repo to answer.

plroebuck commented 5 years ago

An alternative approach would be to use the --delay flag. See delayed root suite in the docs.

iamnikas commented 5 years ago

@plroebuck What package do you advise to use instead of async-request? Axios?

plroebuck commented 5 years ago
const http = require('http');
const axios = require('axios');

const mochaTimeout = 5000;  // Five seconds

before(async function() {
  const self = this;

  const url = 'https://api.exmo.me/v1/ticker/';
  const config = {
    maxContentLength: 32767,    // Allow up to 32k response size
    responseEncoding: 'utf8',
    responseType: 'json',
    timeout: 1000              // Wait up to 1 second for response
  };

  console.log('[ROOT] before hook');

  self.timeout(mochaTimeout);

  await axios
    .get(url, config)
    .then(function(response) {
      // random get item
      let max_items = Object.keys(response.data).length;
      let rnd_index = Math.floor(Math.random() * (max_items - 0)) + 0;

      // get item key
      let ticker = Object.keys(response.data)[rnd_index];
      return ticker;
    })
    .catch(function(err) {
      if (err.response) {
        let code = err.response.status;
        console.error(`[${code}] ${http.STATUS_CODES[code]}`);
      } else if (err.request) {
        console.error(`error: no response from ticker server in ${config.timeout} msecs`);
      } else {
        console.error(`error: request setup: ${err.message}`);
      }
      self.skip();
    })
    .then(function(ticker) {
      const url = `http://test.com?ticker=${ticker}`;
      const suite = {
        title: '[RUN TESTS]',
        tests: [
          {
            title: `[TEST] GET ${url}`,
            url: url
          }
        ]
      };

      genSuite(suite); // <= begin() abuse
    });
});

it('placeholder');

function genSuite(suite) {
  describe(suite.title, function() {
    this.timeout(mochaTimeout);
    suite.tests.forEach(genTest);
  });
}

function genTest(test) {
  it(test.title, async function() {
    const jsonMimeType = 'application/json';
    const config = {
      headers: {
        'Accept': `${jsonMimeType}, */*;q=0`
      },
      maxContentLength: 32767,    // Allow up to 32k response size
      responseEncoding: 'utf8',
      responseType: 'json',
      timeout: 1000              // Wait up to 1 second for response
    };

    await axios
      .get(test.url, config)
      .then(function(response) {
        if (response.headers['content-type'] !== jsonMimeType) {
          throw new Error('unacceptable response');
        }

        console.log(response);
        // assertions here...
      });
  });
}