Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit testing - toHaveResource() - found no resources instead #1850

Open
sebolabs opened this issue Jun 7, 2022 · 14 comments
Open

Unit testing - toHaveResource() - found no resources instead #1850

sebolabs opened this issue Jun 7, 2022 · 14 comments
Labels
bug Something isn't working feature/init cdktf init related features priority/important-longterm Medium priority, to be worked on within the following 1-2 business quarters. size/small estimated < 1 day testing ux/debugging

Comments

@sebolabs
Copy link

sebolabs commented Jun 7, 2022

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

> portal-azure@1.0.0 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 under cdktf.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 sebolabs added bug Something isn't working new Un-triaged issue labels Jun 7, 2022
@sebolabs sebolabs changed the title CDKtf Unit testing - toHaveResource() - found no resources instead Unit testing - toHaveResource() - found no resources instead Jun 7, 2022
@jsteinich
Copy link
Collaborator

@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.

@DanielMSchmidt
Copy link
Contributor

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.

@DanielMSchmidt DanielMSchmidt added testing ux/debugging needs-priority Issue has not yet been prioritized; this will prompt team review priority/important-soon High priority, to be worked on as part of our current release or the following one. size/small estimated < 1 day and removed new Un-triaged issue needs-priority Issue has not yet been prioritized; this will prompt team review labels Jun 16, 2022
@jsteinich
Copy link
Collaborator

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.

@paambaati
Copy link

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);
  });
});

@DanielMSchmidt
Copy link
Contributor

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

@pchaganti
Copy link

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!

@jsteinich
Copy link
Collaborator

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)

@paambaati
Copy link

@jsteinich Thanks, that does help a bit, although it could use some more clarity –

  1. 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 shown TerraformStack and not Construct 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()
})

@jsteinich
Copy link
Collaborator

  1. 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 shown TerraformStack and not Construct 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.

@jmccann
Copy link

jmccann commented Feb 7, 2023

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.

@paambaati paambaati mentioned this issue May 12, 2023
26 tasks
@xiehan xiehan added help wanted Community contributions welcome as the core team is unlikely to work on this soon priority/important-longterm Medium priority, to be worked on within the following 1-2 business quarters. feature/init cdktf init related features and removed priority/important-soon High priority, to be worked on as part of our current release or the following one. help wanted Community contributions welcome as the core team is unlikely to work on this soon labels Jun 1, 2023
@iot-resister
Copy link

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);
});

@Maed223
Copy link
Contributor

Maed223 commented Aug 8, 2023

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.

@dan-developer
Copy link

Same problem here:

it("check if this can be planned", () => {
        const app: App = Testing.app();
        const stack: TerraformStack = new TerraformStack(app, "test");
        expect(Testing.fullSynth(stack)).toPlanSuccessfully();
    });

This outputs:

error TS2339: Property 'toPlanSuccessfully' does not exist on type 'JestMatchers<string>'.

    7         expect(Testing.fullSynth(stack)).toPlanSuccessfully();
                                               ~~~~~~~~~~~~~~~~~~

@dan-developer
Copy link

Same problem here:

it("check if this can be planned", () => {
        const app: App = Testing.app();
        const stack: TerraformStack = new TerraformStack(app, "test");
        expect(Testing.fullSynth(stack)).toPlanSuccessfully();
    });

This outputs:

error TS2339: Property 'toPlanSuccessfully' does not exist on type 'JestMatchers<string>'.

    7         expect(Testing.fullSynth(stack)).toPlanSuccessfully();
                                               ~~~~~~~~~~~~~~~~~~

After change the package versions, it back working correctly. Follow the versions:

"dependencies": {
    "@cdktf/provider-aws": "19.29.0",
    "cdktf": "^0.20.8",
    "constructs": "^10.3.0"
  },
  "devDependencies": {
    "@types/jest": "^29.5.12",
    "@types/node": "^22.1.0",
    "jest": "^29.7.0",
    "ts-jest": "^29.2.4",
    "ts-node": "^10.9.2",
    "typescript": "^5.5.4"
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working feature/init cdktf init related features priority/important-longterm Medium priority, to be worked on within the following 1-2 business quarters. size/small estimated < 1 day testing ux/debugging
Projects
None yet
Development

No branches or pull requests

10 participants