I'm unit testing my ComponentResources using Mocha...
# typescript
l
I'm unit testing my ComponentResources using Mocha and async functions. I call
expect()
directly from the async function (passed as a parameter to
it()
), unless there's an
Output
or
Promise
involved, in which case I call
expect()
from within an
apply()
. When everything passes, all good. And tests without
apply
fail, all is good too: Test Explorer shows me my errors. But when an
expect()
inside an
apply()
fails, Test Explorer shows the test as passing. I see the error message in VSCode's Output window, or in the console if that's where I running them, but Test Explorer isn't noticing that there was a failed
expect()
. The failures always show up as *UnhandledPromiseRejectionWarning*s. What am I doing wrong?
I can get the correct behaviour by expecting the apply to not throw an exception, but that makes for clunky code. Is this the best way to do it?
Copy code
it('should do stuff', function checkStuff() {
  expect(stuff.apply(val => {
    expect(val).to.eql("stuff");
  })).not.to.throw();
});
I stlil get the UnhandlePromiseRejectionWarning, so I feel there must be a better way....
This works, but it's even clunkier:
Copy code
it('should do stuff', function checkStuff() {
  return new Promise(function (resolve, reject) {
  try {
    stuff.apply(val => {
    expect(val).to.eql("stuff");
    resolve();
  catch (error) {
    reject(error);
  }
  });
});
Looks like chai-as-promised has the right idea. I could use it if I added a
.then()
method to pulumi.Output.. if such a thing is possible...
f
You could do something along these lines to interface w/ the promises way of doing things: https://github.com/pulumi/pulumitv/blob/master/modern-infrastructure-wednesday/2020-05-13/resource-compare/src/test-helpers.ts
(if I’m understanding what I think you’re trying to do)

https://youtu.be/ydR61dZmKgU?t=739

has an example of using that style of helper within a test
l
(back now) Ooo yes, that looks like it would work nicely.. let me give that a go.
Ok I have it working, and a bit smaller. There's a few improvements I'd like to make, but I think they're not really Pulumi-specific, so maybe this isn't the place to deal with them. I'll ask anyway: nothing ventured, nothing gained!
So what I have working is this little function at the top of my test file:
Copy code
function promise<T>(output: pulumi.Output<T>): Promise<T | undefined> {
  return (output as any).promise() as Promise<T>;
}
This allows me to write tests like this:
Copy code
it('should work', async function workTest() {
  expect(await promise(objectUnderTest.unappliedOutput)).to.eql("expected output");
});
This does the job. My questions (so far) are: Where is the
promise()
function defined? The one called in the
return
line of the first snippet? Can I hide the
await
in that function? I haven't found a way to do that yet. I've tried making it async and returning the awaited promise. That produces the expected output on failure, but on success it always produces an error to the effect of '{} doesn't equal "expected output"`. Can
pulumi.Output
or
pulumi.OutputInstance
be extended? It seems they can't because they're types not classes. I'd like to be able to make the
promise()
function an extension method of Output. If I could sort out those niggles, then I could end up with fairly elegant tests like this:
Copy code
it('should work', async function workTest() {
  expect(objectUnderTest.unappliedOutput.promise()).to.eventually.eql("expected output");
});
Ah.. another gotcha. Where
apply()
on an Output property on an Output object will resolve everything, this local
promise()
function only resolves the thing being passed to it. For example, I have a
VpcDhcpOptions
Output object, but I can't use
expect(await promise(options.domainNameServers))
, I have to use
expect(await promise((await promise(options)).domainNameServers)
...
l
Awesome! That solved it. If anyone ever reads this far in this thread and wants to get fluent assertions on Outputs working with chai-as-promised, here is the solution. 1) Use chai-as-promised.
Copy code
import { expect, use as chaiWillUse } from "chai";
import * as chaiAsPromised from "chai-as-promised";
chaiWillUse(chaiAsPromised);
2) Sneakily import the needed
@internal
function:
Copy code
declare module '@pulumi/pulumi' {
  export interface OutputInstance<T> {
    promise(withUnknowns?: boolean): Promise<T>;
  }
}
3) Win.
Copy code
it('should work', async function workTest() {
  return expect(objectUnderTest.unappliedOutput.promise()).to.eventually.eql("expected output");
});
🎉 1