mochajs / mocha

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

Enhanced control of test context #3485

Closed papercuptech closed 6 years ago

papercuptech commented 6 years ago

Prerequisites

Description

When I install and use 'mocha-ctx', I get enhanced control of context! It seems like it keeps doing this every time I use it with mocha.

Steps to Reproduce

npm i mocha-ctx

require('mocha-ctx')
describe('mocha-ctx', () => {
  before(() => {
    context({
      cool: 'stuff!',
      fn: () => 'I see cool ' + context().cool
    })
  })
  it('can do this', function() {
    this.someProp = 10
    assert(this === context())
  })
  it('can now provide context (i.e. "this") to lambdas', () => {
    assert(context().someProp === 10)
  })
  describe('layer 1', () => {
    it('can do other stuff too', () => {
      // this will NOT create a 'cool' property on layer 1 context
      // but will actually set 'cool' property at top context (or wherever
      // 'cool' was defined in context hierarchy)
      context().cool = 'way'
      assert(context().fn() === 'I see cool way')
      context().timeout(-42)
      context().skip()
    })
  })
})

Expected behavior: [What you expect to happen] That someone, somewhere will jump up and down in joy.

Actual behavior: [What actually happens] That they will smile too!, and that mocha will want these features as part of mocha.

Reproduces how often: [What percentage of the time does it reproduce?] 100%

Versions

1.0.0-a.0

Additional Information

Additional 'bugs'

More details at https://github.com/papercuptech/mocha-context

plroebuck commented 6 years ago

Have you taken a look at PR #3399?

papercuptech commented 6 years ago

@plroebuck That PR appears to not be backwards compatible and seems to primarily address providing lambdas access to context.

mocha-ctx also enables lambdas to access context, but in a backwards compatible way (i.e. does not require changing what is passed to lambda, so passing 'done' still works). Further, this is a minor feature of mocha-ctx, but should address #2767, #2657, #2018, #1856.

mocha-ctx's larger value is being able to let lower contexts set higher context properties that are being shared, and that each test truly has its own context, which should address #2014, #2140, #2914, #2977, #797.

mocha-ctx also provides control of globals in context; i.e. things running in a given context can see globals differently, or be restricted from even accessing some, and mocha-ctx will correctly housekeep saving and restoring as hierarchical contexts are entered and left, which should address #2656.

Additionally, the behavior mocha-ctx provides is only explicitly enabled for a given context (and all things running in it), so you can have some sub-contexts that use it and some that don't, which should allow for easier adoption.

Lastly, all these features should make it much easier to compose contexts and tests, so more types of integration testing can more easily be done with mocha.

papercuptech commented 6 years ago

@plroebuck things like this are now a little easier.


var function testUtil() {
  assert(context().testProp === true)
}

var tests = () => {
    it('uses shared context', () => {
      assert(context().shared === 1)
    })
    it('uses test context', () => {
      context().testProp = true
      testUtil()
    })
    it('uses test context, but fails', () => {
      // testUtil will fail, as 'testProp' not defined;
      // each test now has own context
      testUtil()
    })
    it('uses global', () => {
      assert(someGlobal === 'read only')
    })
    it('sets shared context', function() {
      this.shared = 42
      // context().shared = 42 would work too
    })
    it('really is "this" context', () => {
      context().skip()
    })
}

describe('define context tests need', () => {
  before(() => {
    // here is where 'magic' happens, and also activates per-test context
    context({
      shared: 0,
      sharedFn: () => context().shared - 42
    })
  })

  describe('provide specific context', () => {
    before(() => {
      context().shared = 1

      context({
        globals: {
          someGlobal: undefined
          // now anything that gets or sets 'someGlobal' will throw
        }
      })
    })

    // fails merely from attempting to access (get) global 'someGlobal'
    tests()
  })

  // context can still be used as alias for 'describe', but not other way around
  context('provide some other specific context', () => {
    before(() => {
      context().shared = 1000
      // can get but not set 'someGlobal'
      context({globals: {someGlobal: 'read only'}})
    })

    // fails on context().shared not being 1 (it's 1000)
    tests()

    it('shared', function() {
      assert(this.shared === 42)
      assert(this.sharedFn() === 0)
    })
  })

})
Munter commented 6 years ago

What is the value of mocha-ctx over just using scope defined variables?


describe('mocha with context variables', () => {
  const context = {
    cool: 'stuff!',
    fn: () => 'I see cool ' + context.cool
  };

  it('can not do this: what would the point of that even be anyway?', function() {
    assert(this === context)
  })

  describe('layer 1', () => {
    it('can do other stuff too', () => {
      // this will NOT create a 'cool' property on layer 1 context
      // but will actually set 'cool' property at top context (or wherever
      // 'cool' was defined in context hierarchy)
      context.cool = 'way'
      assert(context.fn() === 'I see cool way')
      this.timeout(-42)
      this.skip()
    })
  })
})
papercuptech commented 6 years ago

@Munter In your example, the test 'can do other stuff too' can not be composed into other describes. Your example test is the same functionally, but is coupled and hard coded to the const context. You can do what you've shown, but mocha-ctx offers much more compos-ability. Look at my comment and sample code just preceding your comment.

Your test can not do this: what... is missing the point. mocha-ctx lets () => {} get tothis via context() without passing as an argument and thus not breaking compatibility; in fact anything can get to context().

Bamieh commented 6 years ago

Thanks for the serious effort writing this.

Sadly I feel that this feature adds extra complexity to Mocha.

papercuptech commented 6 years ago

@Bamieh

papercuptech commented 6 years ago

@Bamieh Consider these simplifications:

If you truly believe these things are not of the same ethos as mocha, and testing generally, please feel free to close this issue, although it would be interesting to hear from others on the matter.

aleung commented 6 years ago

Calling context() rather then this would be helpful. Then I can always use arrow function. Now I have to temporary disable the tslint rule only-arrow-functions in order to access this in test case.

papercuptech commented 6 years ago

@Bamieh

context() becomes the singular way to access test context.

A Controlled Environment is essential to testing

@Bamieh Would you please elaborate on why you believe mocha should not be providing this kind of control over the environment; I had previously believed controlling the environment in which things are tested within was exactly what mocha was meant to facilitate.

papercuptech commented 6 years ago

Out of scope