Open adworacz opened 1 year ago
Thanks for the feedback, great to hear people are getting value from it!
You have the concrete need here; programming by wishful thinking, what would example tests look like? Existing matchers that could be good starting points for an API:
Jest's toThrow
:
You can provide an optional argument to test that a specific error is thrown:
- regular expression: error message matches the pattern
- string: error message includes the substring
- error object: error message is equal to the message property of the object
- error class: error object is instance of class
Jest Extended's .toThrowWithMessage
:
...checking if a callback function throws an error with a given error type and given error message. Message can either be a
String
or aRegExp
.
Jest Extended's .toSatisfy
:
use a custom matcher by supplying a predicate function that returns a
Boolean
.
(good feedback on failure is harder here as the test has no idea what the meaning of the predicate is)
Would it be an argument to .toThrow
, which would either: be mandatory, meaning a breaking API change; be optional, adding complexity when dealing with whether or not the options were supplied; or be in the supplied options (as what, matching
, satisfying
, ...)? Or would it be an additional matcher (called what, toErrorWith
, toErrorMatching
, toErrorSatisfying
, ...)?
Hmm, these are great questions!
Off the cuff, I'd say our initial needs are:
We write our own custom error classes occasionally and need to assert on their types as well as their message contents.
So something along the lines of:
await expect(throwError(() => new CustomError("oh no!"))).toError(/oh no/);
await expect(throwError(() => new CustomError("oh no!"))).toError('oh no');
await expect(throwError(() => new CustomError("oh no!"))).toError(CustomError, /oh no/);
await expect(throwError(() => new CustomError("oh no!"))).toError(CustomError, 'oh no');
await expect(throwError(() => new CustomError("oh no!"))).toError(CustomError);
(Just operating with the existing toError
matcher, totally cool with a new dedicated matcher)
Another option is simply being able to access the actual error and then reuse all of the existing error matching capability provided by Jest, like:
await expect(throwError(() => new CustomError("oh no!"))).toError((e) => {
expect(e).toBeInstanceOf(CustomError);
expect(e).toBe(new CustomError("oh no!");
expect(e.message).toMatch(/oh no/);
expect(e.customProperty).toBe(foobar);
});
As for your other questions:
toError()
mandatory, so no breaking changes. Having optional args would be plenty..toError(CustomError, /foo bar/)
is a lot cleaner to write (and read) than .toError({ instanceOf: CustomError, matching: /foo bar/})
v0.7.0 has the first iteration of this: .toErrorWith
, just covering the regex matcher so far.
Awesome, thank you! I'll give it a try soon and report back any issues that I may have.
In my local testing this is working well, thank you for adding it!
We still have a use case for deeper error object introspection, and .toBeInstanceOf()
styled checks, but this is a great start!
(This might be worth it's own separate Issue, which I'm happy to cut if desired)
Speaking on the deeper error object introspection, I think this is something of a limitation with toEmit()
as well. More specifically, it works well on primitive values, but it's less adaptable for more complex object types.
Effectively, we have a need to validate a set of properties on an emitted object. expect.objectContaining
gets pretty far here, allowing you to do:
await expect(getObject()).toEmit(expect.objectContaining({ foo: 'baz', bar: expect.stringContaining('example')}))
However, you can't run any more complex validation on the emitted object, or mix objectContaining with a strictEquals, like so:
....toEmit(expect.objectContaining({
foo: 'baz',
nestedValue: expect.strictEquals({ //Doesn't work, as Jest doesn't expose this as an asymmetric operator
a: 'b',
c: 'd',
}),
})
With all this in mind, being able to access the actual emitted value/object would allow for much more complex logic, something like:
...toEmit((object) => {
expect(object.foo).toBe('baz')
expect(object.nestedValue).toStrictEqual({...})
// etc etc.
})
I don't think that's this library's responsibility. RxJeSt just exposes the observable data to the tests in a predictable way, if you want to run arbitrary expectations on it you might want something like .toSatisfy
.
Not sure how you'd like to implement this, but we have a lot of use in asserting that specific errors are thrown/returned in our Observables.
I don't see an obvious way on how to assert what kind of errors are thrown with
.toError()
. I figure either being able to specify a custom assertion callback, and/or a regex/string matcher would go a long way.Love this project by the way, keep up the good work!