There are many instances in testing which render strictNullChecks and other type safety checks counterproductive.
For example error TS2531: Object is possibly 'null'.. In my real app, this is very useful information, but during testing it's common to be testing for the presence of this which may indeed be null, hence the test. Annotating the files with thing! feels like 'lying' to the compiler.
Other frustrations are places where we have implicit this issues. If you want to assign values to be used in testing, like this.server = startMockServer(), this is considered an error as property server doesn't exist on TestContext etc. We can override this by adding this: any to the beforeEach hooks. Perhaps the blueprints could add this by default, or we could add to the documentation.
I wonder if it would be possible to take the approach of folder level tsconfig
e.g. in the test folder
{
"extends": "../tsconfig.json",
"compilerOptions": {
"strictNullChecks": false
}
}
It’s in principle possible, though we’d have to do some work on the invocations to make it work. However, don’t think we want to do that! Here’s why:
If your types are accurate and you have e.g. strictNullChecks, you no longer need to write tests around things being null in certain places. I strongly recommend picking either tests or types to handle the nullability issues. If you do want to use both, then using ! or an explicit cast is precisely the thing you'll need to do.
You can also easily work around the types for this in the test context. Here’s what we do:
type Context = TestContext & {
server: MirageServer;
someReusedValue: string;
}
module('foo', function(hooks) {
hooks.beforeEach(function(this: Context) {
this.server = startMockServer();
this.someReusedValue = 'waaaat';
});
test('it works!', function(this: Context) {
// `this.server` and `this.someReusedValue` are both available here
});
});
What if you have an integration test and you want to set the properties that you are passing to the component
test('it yields the provided block', async function (assert) {
this.message = 'The system only dreams in total darkness';
await render(hbs`{{#AlertBox}}{{this.message}}{{/AlertBox}}>`);
assert.dom('.alert-body').hasText(this.message);
});
This will produce the same error _(Property 'message' does not exist on type 'TestContext')_ although it is working.
@mupkoo the same thing works there (though I'd strongly suggest there's no reason to use (edit: see below).this in the specific example you give there – just use let or const to bind a local value)
type Context = TestContext & { message: string };
test('it yields the provided block', async function (this: Context, assert) {
this.message = 'The system only dreams in total darkness';
await render(hbs`{{#AlertBox}}{{this.message}}{{/AlertBox}}>`);
assert.dom('.alert-body').hasText(this.message);
});
Folks landing here may want to read the TS 2.0 announcement blog post's discussion of this types for functions to get a better handle on how this works!
I know that the example that I gave is simple and can be done differently. For example:
test('it yields the provided block', async function (assert) {
this.set('message', 'The system only dreams in total darkness');
await render(hbs`{{#AlertBox}}{{this.message}}{{/AlertBox}}>`);
assert.dom('.alert-body').hasText(this.get('message'));
});
but in a way this.set('key'... is the same as this.key = and I think that Ember is trying to move away from this.
For now I just monkey patch the interface for all of my tests
declare module 'ember-test-helpers' {
interface TestContext {
[key: string]: any;
}
}
So: I apologize. I wanted to give you an answer but was very tired and I wrote something stupid above; you obviously can't set a value in a template without using this for the template context.
As such, you definitely need something like what you wrote there for setting a value on the backing this in a test, and you need that for passing values into a template invocation. I believe the this type is set up so that this.set('whatever') does work, but this is definitely a thing we should consider as we evaluate testing APIs and TS going forward.
(cc. @rwjblue – I'd love to talk with you about ways we can make this path smoother for TS users.)
To me, having [key: string]: any; is more convenient than having to create a case specific types, although I am aware that this way I lose some of the TypeScript checks.
I'm gonna close this issue as I feel like the tips offered here render my suggestion unnecessary. Thanks folks!
Most helpful comment
It’s in principle possible, though we’d have to do some work on the invocations to make it work. However, don’t think we want to do that! Here’s why:
If your types are accurate and you have e.g.
strictNullChecks, you no longer need to write tests around things beingnullin certain places. I strongly recommend picking either tests or types to handle the nullability issues. If you do want to use both, then using!or an explicit cast is precisely the thing you'll need to do.You can also easily work around the types for
thisin the test context. Here’s what we do: