terraform-cdk
terraform-cdk copied to clipboard
Unit testing - toHaveResource() - found no resources instead
Community Note
- Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
- Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request
- If you are interested in working on this issue or have submitted a pull request, please leave a comment
cdktf & Language Versions
cdktf 0.11.0 (type-script)
Affected Resource(s)
Unit tests not working like I would expect. Could be me though :)
Debug Output
N/A
Expected Behavior
Test should pass unless I'm doing something wrong.
Actual Behavior
> [email protected] test
> jest
FAIL __tests__/main-test.ts (9.099 s)
● Configuration › should contain an application
Expected azuread_application with properties {} to be present in synthesised stack.
Found no azuread_application resources instead
28 | new PortalStack(scope, 'test');
29 | })
> 30 | ).toHaveResource(Application);
| ^
31 | });
at Object.<anonymous> (__tests__/main-test.ts:30:7)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 0 todo, 0 passed, 1 total
Snapshots: 0 total
Time: 4.642 s
Steps to Reproduce
import "cdktf/lib/testing/adapters/jest";
import { Testing } from "cdktf";
import { PortalStack } from "../main";
import { Application } from "@cdktf/provider-azuread";
describe("Configuration", () => {
it("should contain an application", () => {
expect(
Testing.synthScope((scope) => {
new PortalStack(scope, 'test');
})
).toHaveResource(Application);
});
});
Important Factoids
-
cdktf synth/diff/deploy
work perfectly fine -
cdk.tf.json
undercdktf.out
contains the following:
[
[...]
"resource": {
"azuread_application": {
"xxxauthportal_portalapp_3876DFED": {
"//": {
"metadata": {
[...]
]
References (main.ts)
import { Construct } from "constructs";
import {
App,
TerraformStack,
RemoteBackend,
} from "cdktf";
import {
AzureadProvider,
ApplicationFeatureTags,
Application,
} from "./.gen/providers/azuread";
export class PortalStack extends TerraformStack {
constructor(scope: Construct, name: string) {
super(scope, name);
new AzureadProvider(this, "AzureAd", {});
this.AzureAdApp();
}
AzureAdApp(this: PortalStack) {
const samlIdUri = "";
const samlReplyUrl = "";
const samlLogoutUrl = "";
const appFeatureTags: ApplicationFeatureTags = {
enterprise: true,
};
new Application(this, "portal_app", {
displayName: `AuthPortal`,
featureTags: [appFeatureTags],
identifierUris: [samlIdUri],
web: {
redirectUris: [samlReplyUrl],
logoutUrl: samlLogoutUrl,
},
preventDuplicateNames: true,
});
}
}
const app = new App();
const stack = new PortalStack(app, "auth-portal");
new RemoteBackend(stack, {
hostname: "app.terraform.io",
organization: "xxx",
workspaces: {
name: `auth-portal`,
}
});
app.synth();
@sebolabs Currently you can't pass a stack instance to Testing.synthScope
. You can work around by making your class extend from Construct
instead. Take a look here for more info.
While we seem to have answered the question I'd like to keep this issue open and add a proper warning / error if someone passes a stack into synthScope.
While we seem to have answered the question I'd like to keep this issue open and add a proper warning / error if someone passes a stack into synthScope.
If not too difficult, actually just allowing a stack would be more user friendly.
Can you folks help understand the solution here?
If I pass in a Construct
instead of a Stack
, I still don't get any generated resources from synthScope
.
This is how I've modified the above example –
import "cdktf/lib/testing/adapters/jest";
import { Testing } from "cdktf";
+import { Construct } from "constructs";
import { PortalStack } from "../main";
import { Application } from "@cdktf/provider-azuread";
+ class PortalConstruct extends Construct {
+ constructor(scope: Construct, options: Record<PropertyKey, unknown>) {
+ super(scope, `${options.environment}-construct`)
+ new PortalStack(scope, environment)
+ }
+}
describe("Configuration", () => {
it("should contain an application", () => {
expect(
Testing.synthScope((scope) => {
+ new PortalConstruct(scope, 'test');
- new PortalStack(scope, 'test');
})
).toHaveResource(Application);
});
});
This is because your construct contains a stack. A stack does not synthesize into JSON like a construct would, but it writes a file to disk with it's childrens JSON in it. This hides the content from the testing marchers. You should abstract the things you do in a stack in one or more constructs and test how they work together here
Running into the same issue. Is there any complete example that can be posted by the team to make it easier for users who run into this issue with testing?
thanks!
Running into the same issue. Is there any complete example that can be posted by the team to make it easier for users who run into this issue with testing?
thanks!
Hopefully this helps. There are also internal tests of the testing framework now (for example: https://github.com/hashicorp/terraform-cdk/tree/main/test/python/testing-matchers)
@jsteinich Thanks, that does help a bit, although it could use some more clarity –
- The inline comment in the linked example says
// Could be a class extending from Construct
but in other parts of the guide, we're always shownTerraformStack
and notConstruct
as examples.
Additionally, not all of us use Jest, even though it is the most popular testing framework. For folks that use other frameworks, it would help if there was an example that shows how to use Constructs
. I for one use tap
, and I've managed to make this work with a combination of reading the original Jest plugin implementation and comments from this issue –
import tap from 'tap'
import expect from 'expect'
import { Testing, testingMatchers } from 'cdktf'
import { MyConstruct } from '../main'
import { Record as CFDNSRecord } from '../.gen/providers/cloudflare'
import type { TerraformConstructor } from 'cdktf/lib/testing/matchers'
tap.Test.prototype.addAssert(
'toHaveResource',
3,
// NOTE: has to be a regular function and not an arrow function
// for the `this` binding to work correctly.
function toHaveResource(
hcl: string,
message: string,
resource: {
ctor: TerraformConstructor
props: Record<string, string>
},
) {
const msg = message || 'Terraform stack is missing property'
const toHaveResourceWithProperties =
testingMatchers.getToHaveResourceWithProperties(
(items: Array<string>, assertedProperties: Record<string, unknown>) => {
if (Object.entries(assertedProperties).length === 0) {
return items.length > 0
}
return expect
.arrayContaining([expect.objectContaining(assertedProperties)])
.asymmetricMatch(items)
},
)
const self = this as unknown as typeof tap
return self.ok(
toHaveResourceWithProperties(hcl, resource.ctor, resource.props).pass,
msg,
resource,
)
},
)
void tap.test('infra > should include all resources', (t) => {
t.plan(1)
const hclJson = Testing.synthScope((scope) => {
new MyConstruct(scope)
})
t.toHaveResource(hclJson, 'should have the Cloudflare DNS record', {
ctor: CFDNSRecord,
props: { type: 'CNAME' },
})
t.end()
})
- The inline comment in the linked example says
// Could be a class extending from Construct
but in other parts of the guide, we're always shownTerraformStack
and notConstruct
as examples.
https://www.terraform.io/cdktf/concepts/constructs has some more information on constructs, including an example and a comparison to stacks.
I for one use
tap
, and I've managed to make this work
That's great. You can also use the Testing
class directly which could possibly reduce the amount of adapter code needed.
I just ran across this issue. I used cdktf init --template=typescript
to create a project. I then started populating my generated project with resources and started populating my generated test examples with tests.
Then I ran into Found no github_issue_label resources instead
even though it was.
Ran into this issue from a search. I didn't see anything from docs I came across of the need to use Construct
if I want to do unit testing.
Seems cdktf init
generates project using TerraformStack
which sounds like from this issue is not compatible with the unit test examples it generates for unit testing.
As a new user the docs and generated code don't seem to talk about this issue ... and would be nice if initial setup worked more out of the box.
I've read all the links here and I'm still confused about what a MyApplicationsAbstraction
looks like. I also don't understand why my example isn't working? Thanks for the awesome library!
it("should contain a container", () => {
const stack = new OnPremStack(Testing.app(), "learn-cdktf-docker");
const synthesized = Testing.synth(stack);
expect(synthesized).toHaveResource(Container);
});
Hey @iot-resister!
I've read all the links here and I'm still confused about what a
MyApplicationsAbstraction
looks like.
MyApplicationsAbstraction
is a bit ambiguous as it is written in the Typescript documentation as it at times refers to a Construct, and at other times a Stack. What's important to note is that in your usage (i.e. OnPremStack
) it refers to a Stack, as the instance of OnPremStack
is being passed into Testing.synth
directly.
I also don't understand why my example isn't working? Thanks for the awesome library!
Hard to say exactly what the issue is from the snippet you've provided, could you provide a bit more context on what OnPremStack
looks like and the specific issue you're running into? One thing that comes to mind is that the Container
instance is within another Stack that is nested in OnPremStack
.