---
title: 'اختبار AWS CDK في TypeScript: اختبر قبل النشر'
description: 'تعلم اختبار AWS CDK في TypeScript: اكتب fine-grained assertions، و snapshot tests، و Aspect validation باستخدام Jest لاكتشاف أخطاء الـ infra قبل الـ deploy.'
url: https://nerdleveltech.com/ar/aws-cdk-testing-typescript
language: ar
date: '2026-06-18'
category: cloud-devops
tags:
  - aws cdk
  - typescript
  - testing
  - jest
  - cloud-devops
  - infrastructure as code
  - assertions
  - aws
markdown_url: https://nerdleveltech.com/ar/aws-cdk-testing-typescript.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/aws-cdk-testing-typescript'
terms: https://nerdleveltech.com/llms.txt
---

# اختبار AWS CDK في TypeScript: اختبر قبل النشر

لاختبار وحدة (unit-test) لـ AWS CDK stack في TypeScript، قم بعمل synthesize له إلى قالب CloudFormation باستخدام `Template.fromStack()` وتحقق من ذلك القالب باستخدام وحدة `aws-cdk-lib/assertions` و Jest. لا يتم نشر أي شيء ولا يلزم وجود حساب AWS.

## ملخص

يضيف هذا الدليل العملي مجموعة اختبار كاملة إلى تطبيق AWS CDK في TypeScript حتى تكتشف أخطاء البنية التحتية قبل تشغيل `cdk deploy`. ستقوم بإنشاء مشروع باستخدام CDK CLI، وبناء construct صغير لـ `IngestPipeline` (S3 bucket مشفر ومفعل به خاصية الإصدارات بالإضافة إلى Lambda function)، ثم كتابة **fine-grained assertions**، و **partial matchers**، و **value captures**، و **snapshot test**، واختبار حوكمة باستخدام **Aspects and Annotations** — كل ذلك باستخدام **Jest 30.4.2**[^jest] ووحدة assertions الخاصة بـ **aws-cdk-lib 2.260.0**[^lib]. تم تنفيذ كل اختبار هنا مقابل synth حقيقي في 18 يونيو 2026: **3 مجموعات، 10 اختبارات، وجميعها ناجحة**، بدون بيانات اعتماد AWS وبدون نشر أي شيء. الميزانية الزمنية حوالي 30-40 دقيقة.

## ما ستتعلمه

- كيفية إنشاء مشروع CDK TypeScript مهيأ لـ Jest
- كيفية كتابة fine-grained assertions مقابل قالب CloudFormation الناتج
- كيفية استخدام `Match` للمطابقة الجزئية، والمستقلة عن الترتيب، ومطابقة "يجب أن يكون غائباً"
- كيفية التقاط القيم المولدة (مثل `Ref`) والتحقق منها بشكل متقاطع
- كيفية التحقق من السياسات على مستوى الموارد مثل `DeletionPolicy: Retain`
- متى تفيد اختبارات اللقطة (snapshot tests) — وفخ التراجع الذي تخفيه
- كيفية اختبار قواعد الحوكمة باستخدام Aspects و `Annotations`
- كيفية تشغيل المجموعة الكاملة في CI بدون حساب AWS

## لماذا نختبر CDK على الإطلاق

يبدو كود CDK مثل التطبيق، لكنه ينتج قالب CloudFormation. خطأ مطبعي في سياسة bucket، أو Lambda ببيئة تشغيل خاطئة، أو construct يسقط التشفير بهدوء لن يفشل في التجميع (compile) — بل سيتم نشره *ثم* يتسبب في حادثة. يشحن CDK مكتبة assertions تفحص القالب الناتج دون الاتصال بـ AWS، لذا يمكنك إثبات أن الـ stack الخاص بك ينتج الموارد التي تقصدها. هذه هي نفس الفكرة وراء مراجعة خطط Terraform قبل التطبيق، والتي تمت تغطيتها في [درس قفل حالة S3 الأصلية في Terraform](https://nerdleveltech.com/terraform-s3-native-state-locking-tutorial)؛ هنا يحدث التحقق في مشغل الاختبار الخاص بك.

تعمل الاختبارات أدناه في أجزاء من الثانية ولا تحتاج إلى شبكة، لذا فهي تنتمي إلى نفس فحص طلب السحب (pull-request) الذي يشغل اختبارات تطبيقك. يفشل كل منها بصوت عالٍ في اللحظة التي يغير فيها إعادة الهيكلة (refactor) البنية التحتية المولدة، وهو بالضبط ما يريد المراجع معرفته — وليس بعد وصول التغيير بالفعل إلى البيئة.

## المتطلبات الأساسية

- **Node.js 22+** (استخدم الصندوق الرملي Node 22.22.3؛ Node 20 أو أحدث يعمل)
- **npm 10+**
- **لا يلزم حساب AWS أو بيانات اعتماد** — الـ synthesis محلي تماماً
- الإصدارات المثبتة المستخدمة في الدليل: **aws-cdk-lib 2.260.0**[^lib]، **aws-cdk (CLI) 2.1128.0**، **constructs 10.6.0**، **TypeScript 5.9.3**، **Jest 30.4.2**[^jest]، **ts-jest 29.4.11**، **@types/jest 30.0.0**، **@types/node 24.13.2**

## الخطوة 1: إنشاء مشروع CDK TypeScript

أنشئ مجلداً فارغاً واجعل CDK CLI يولد تطبيق TypeScript. استخدام `npx` يتجنب التثبيت العالمي.

```bash
mkdir ingest-pipeline && cd ingest-pipeline
npx --yes aws-cdk@2.1128.0 init app --language TypeScript
```

يقوم القالب الأساسي بالفعل بإعداد Jest. يبدو ملف `jest.config.js` المولد هكذا[^scaffold]:

```js
module.exports = {
  testEnvironment: 'node',
  roots: ['<rootDir>/test'],
  testMatch: ['**/*.test.ts'],
  transform: {
    '^.+\\.tsx?$': 'ts-jest',
  },
  setupFilesAfterEnv: ['aws-cdk-lib/testhelpers/jest-autoclean'],
};
```

يقوم مساعد `jest-autoclean` بتسجيل هوك `afterAll` الذي يحذف مجلدات cloud-assembly المؤقتة التي ينشئها كل synth، بحيث لا تترك عملية تشغيل اختبار كبيرة مجلدات synthesis مؤقتة وراءها[^autoclean].

تعديل واحد يجعل المخرجات نظيفة. يستخدم ملف `tsconfig.json` الخاص بالقالب `module: "NodeNext"`، وتحت هذا الإعداد يطبع ts-jest تحذير hybrid-module `TS151002` ما لم يتم تشغيل isolated modules. أضف سطراً واحداً إلى `compilerOptions`:

```json
{
  "compilerOptions": {
    "isolatedModules": true
  }
}
```

لتثبيت الإصدارات الدقيقة التي تم التحقق من هذا الدليل بها، قم بتثبيتها صراحة:

```bash
npm install aws-cdk-lib@2.260.0 constructs@10.6.0
npm install -D aws-cdk@2.1128.0 jest@30.4.2 ts-jest@29.4.11 \
  @types/jest@30.0.0 @types/node@24.13.2 TypeScript@5.9.3 ts-node@10.9.2
```

## الخطوة 2: تحديد البنية التحتية قيد الاختبار

استبدل `lib/ingest-pipeline.ts` بـ construct صغير قابل لإعادة الاستخدام: S3 bucket محصن ومعالج Lambda يقرأ منه. هذا هو الكود الذي ستحاسبه اختباراتك.

```TypeScript
import { Construct } from 'constructs';
import { Duration, RemovalPolicy } from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as lambda from 'aws-cdk-lib/aws-lambda';

export interface IngestPipelineProps {
  /** Timeout for the processor function. Defaults to 30 seconds. */
readonly processorTimeout?: Duration;
}

export class IngestPipeline extends Construct {
public readonly bucket: s3.Bucket;
public readonly processor: lambda.Function;

constructor(scope: Construct, id: string, props: IngestPipelineProps = {}) {
super(scope, id);

this.bucket = new s3.Bucket(this, 'Raw', {
      versioned: true,
      encryption: s3.BucketEncryption.S3_MANAGED,
      enforceSSL: true,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      removalPolicy: RemovalPolicy.RETAIN,
});

this.processor = new lambda.Function(this, 'Processor', {
      runtime: lambda.Runtime.NODEJS_22_X,
      handler: 'index.handler',
      code: lambda.Code.fromInline('exports.handler = async () => ({ ok: true });'),
      timeout: props.processorTimeout ?? Duration.seconds(30),
      environment: { RAW_BUCKET: this.bucket.bucketName },
});

this.bucket.grantRead(this.processor);
}
}
```

بعض الخيارات تهم الاختبارات أدناه: `versioned: true` وتشفير `S3_MANAGED`، و `enforceSSL` (الذي يضيف سياسة bucket تمنع الوصول غير المشفر بـ TLS)، و `RemovalPolicy.RETAIN` (الذي يصبح CloudFormation `DeletionPolicy`)، و `grantRead` (الذي يولد سياسة IAM). Node 22 هي بيئة تشغيل Lambda الحالية المدعومة حتى أبريل 2027؛ يتوفر أيضاً `NODEJS_24_X` إذا كنت تفضل الإصدار الأحدث[^runtimes].

قم بتغليف الـ construct في stack في `lib/app-stack.ts`:

```TypeScript
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { IngestPipeline } from './ingest-pipeline';

export class AppStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
new IngestPipeline(this, 'Ingest');
}
}
```

## الخطوة 3: كتابة أول Fine-grained Assertion

يتحقق الـ fine-grained assertion من شيء واحد محدد حول القالب المولد — "نوع المورد هذا موجود بهذا العدد من المرات" أو "هذا المورد له هذه الخاصية". إنه العمود الفقري لاختبار CDK والافتراضي الصحيح لاكتشاف التراجعات[^testing]. أنشئ `test/ingest-pipeline.test.ts`:

```TypeScript
import { App, Stack, Duration } from 'aws-cdk-lib';
import { Template, Match, Capture } from 'aws-cdk-lib/assertions';
import { IngestPipeline } from '../lib/ingest-pipeline';

function synth(props = {}): Template {
const app = new App();
const stack = new Stack(app, 'TestStack');
new IngestPipeline(stack, 'Ingest', props);
return Template.fromStack(stack);
}

test('creates exactly one encrypted, versioned bucket', () => {
const template = synth();
  template.resourceCountIs('AWS::S3::Bucket', 1);
  template.hasResourceProperties('AWS::S3::Bucket', {
    VersioningConfiguration: { Status: 'Enabled' },
});
});
```

يقوم `Template.fromStack(stack)` بعمل synthesis للـ stack في الذاكرة ويعيد كائناً يمكنك الاستعلام عنه. يفشل `resourceCountIs` إذا كان العدد غير صحيح؛ ويفشل `hasResourceProperties` إذا لم يكن لأي مورد من هذا النوع *مجموعة شاملة* (superset) من الخصائص التي تدرجها. قم بتشغيله:

```bash
npm test
```

يجب أن ترى الاختبار ينجح. لم يتم لمس AWS — القالب موجود فقط في الذاكرة.

## الخطوة 4: المطابقة الجزئية باستخدام `Match`

القوالب الحقيقية تتداخل بعمق، ونادراً ما ترغب في التحقق من كل خاصية. تتيح لك أدوات مطابقة `Match` التحقق فقط من الأجزاء التي تهمك[^assertions]. أضف هذه الاختبارات إلى نفس الملف:

```TypeScript
test('bucket uses S3-managed (AES256) encryption', () => {
synth().hasResourceProperties('AWS::S3::Bucket', {
    BucketEncryption: {
      ServerSideEncryptionConfiguration: Match.arrayWith([
        Match.objectLike({
          ServerSideEncryptionByDefault: { SSEAlgorithm: 'AES256' },
}),
]),
},
});
});

test('enforceSSL adds a deny-insecure-transport policy', () => {
synth().hasResourceProperties('AWS::S3::BucketPolicy', {
    PolicyDocument: {
      Statement: Match.arrayWith([
        Match.objectLike({
          Effect: 'Deny',
          Condition: { Bool: { 'aws:SecureTransport': 'false' } },
}),
]),
},
});
});

test('processor runs Node 22, 30s timeout, no reserved concurrency', () => {
synth().hasResourceProperties('AWS::Lambda::Function', Match.objectLike({
    Runtime: 'nodejs22.x',
    Handler: 'index.handler',
    Timeout: 30,
    ReservedConcurrentExecutions: Match.absent(),
}));
});
```

ثلاث أدوات مطابقة تقوم بالعمل الشاق. ينجح `Match.arrayWith([...])` إذا كانت المصفوفة تحتوي على العناصر المدرجة بأي ترتيب — وهو أمر ضروري لأن ترتيب عبارات CloudFormation غير مضمون. يقوم `Match.objectLike({...})` بمطابقة *جزئية* متكررة، لذا يتم تجاهل المفاتيح الإضافية. ويؤكد `Match.absent()` أن المفتاح **غير** موجود — هنا، لإثبات أن الوظيفة ليس لها reserved concurrency. `Match.stringLikeRegexp('...')` (المستخدم لاحقاً) يطابق سلسلة نصية مقابل تعبير نمطي (regex).

تسمح لك نفس أدوات المطابقة المتداخلة (nested matchers) بالتحقق من IAM المُنشأ. نظرًا لأن الـ construct يستدعي `bucket.grantRead(this.processor)`، فإن CDK يُصدر سياسة IAM يحتوي بيانها (statement) على السماح بقراءة الكائنات من الـ bucket. يؤكد هذا الاختبار وجود إذن القراءة:

```TypeScript
test('grantRead wires an IAM policy allowing s3:GetObject', () => {
  synth().hasResourceProperties('AWS::IAM::Policy', {
    PolicyDocument: {
      Statement: Match.arrayWith([
        Match.objectLike({
          Action: Match.arrayWith(['s3:GetObject*']),
          Effect: 'Allow',
        }),
      ]),
    },
  });
});
```

التحقق من السياسة المُصنعة (synthesized policy) يبقي الأذونات التي تنشئها الـ constructs الخاصة بك تحت الاختبار: إذا أدت عملية إعادة هيكلة الكود (refactor) إلى إسقاط استدعاء `grantRead`، فسيختفي بيان `s3:GetObject*` وسيفشل هذا الاختبار قبل أن يصل التغيير إلى الحساب. (لإثبات أن المنح *ليس* أوسع من القراءة، يمكنك بالإضافة إلى ذلك التحقق من غياب إجراءات الكتابة مثل `s3:PutObject` — حيث أن `grantReadWrite` سيظل يحقق التحقق أعلاه، لأنه يتضمن أيضًا `s3:GetObject*`.)

## الخطوة 5: الربط بين القيم المُنشأة باستخدام `Capture`

يقوم CDK بتعيين المعرفات المنطقية (logical IDs) وتوصيل المراجع نيابة عنك، لذا لا يمكنك كتابتها بشكل ثابت (hardcode). يقوم `Capture` بالتقاط القيمة الفعلية المُنشأة أثناء المطابقة حتى تتمكن من التحقق منها بعد ذلك.

```TypeScript
test('processor env var points at the created bucket', () => {
  const template = synth();
  const bucketRef = new Capture();

  template.hasResourceProperties('AWS::Lambda::Function', {
    Environment: { Variables: { RAW_BUCKET: bucketRef } },
  });

  // The captured value is a CloudFormation { Ref: <logicalId> } object.
  const ref = bucketRef.asObject().Ref as string;
  expect(ref).toMatch(/^IngestRaw/);
  const buckets = template.findResources('AWS::S3::Bucket');
  expect(Object.keys(buckets)).toContain(ref);
});
```

بعد تشغيل المطابقة، يعيد `bucketRef.asObject()` القيمة الحقيقية — `{ Ref: 'IngestRawC4D239E4' }`. نؤكد أن متغير Lambda المسمى `RAW_BUCKET` يشير إلى معرف منطقي موجود بالفعل في القالب عبر `findResources`، والذي يعيد خريطة مفاتيحها هي المعرفات المنطقية. ملاحظة هامة: القيمة الملتقطة هي كائن JavaScript عادي، لذا تحقق منها باستخدام مطابقات Jest مثل `toMatch`، وليس باستخدام مطابق CDK `Match.*`.

## الخطوة 6: التحقق من البيانات الوصفية على مستوى المورد باستخدام `hasResource`

ينظر `hasResourceProperties` فقط إلى كتلة `Properties`. للتحقق من السمات على مستوى المورد مثل `DeletionPolicy`، استخدم `hasResource`، الذي يرى المورد بالكامل:

```TypeScript
test('bucket is retained on stack deletion', () => {
  synth().hasResource('AWS::S3::Bucket', {
    DeletionPolicy: 'Retain',
    UpdateReplacePolicy: 'Retain',
  });
});
```

نظرًا لأن الـ construct يضبط `RemovalPolicy.RETAIN`، يجب على CloudFormation الاحتفاظ بالـ bucket إذا تم حذف الـ stack. يحمي هذا الاختبار من تغيير غير مقصود في السياسة قد يجعل بيانات الإنتاج قابلة للحذف.

## الخطوة 7: اختبار اللقطة (Snapshot testing) — ومتى لا يجب الوثوق به

يقوم اختبار اللقطة بتحويل القالب بالكامل إلى صيغة تسلسلية (serialization) ومقارنته بأساس مخزن. أنشئ `test/snapshot.test.ts`:

```TypeScript
import { App, Stack from 'aws-cdk-lib';
import { Template from 'aws-cdk-lib/assertions';
import { IngestPipeline from '../lib/ingest-pipeline';

test('IngestPipeline matches the stored snapshot', () => {
  const app = new App();
  const stack = new Stack(app, 'SnapStack');
  new IngestPipeline(stack, 'Ingest');
  expect(Template.fromStack(stack).toJSON()).toMatchSnapshot();
});
```

تكتب الجولة الأولى الأساس؛ وتفشل الجولات اللاحقة إذا تغير أي شيء. اللقطات مفيدة حقًا لـ **إعادة هيكلة الكود (refactoring)**: عندما تعيد تشكيل كود CDK الخاص بك ولكنك تنوي عدم تغيير المخرجات، فإن اللقطة غير المتغيرة تثبت ذلك.

الفخ هو التعامل مع اللقطات كاختبارات تراجع (regression tests). توجيهات AWS نفسها صريحة في أن اختبار اللقطة *ليس* جيدًا في اكتشاف التراجعات، لأنه يقارن القالب بالكامل والتغييرات غير المتعلقة بالكود — مثل تبني construct لأفضل ممارسة جديدة، أو تحديث CDK Toolkit يضيف بيانات وصفية، أو قيمة سياق متغيرة — يمكن أن تغير المخرجات المصنعة وتكسر اللقطة دون أي اختلاف حقيقي في النشر[^testing]. استخدم اللقطات لتثبيت عمليات إعادة الهيكلة؛ واستخدم التحققات الدقيقة من الخطوات 3-6 لاكتشاف التراجعات. (كود Lambda المضمن لدينا يبقي هذه اللقطة حتمية؛ بينما `Code.fromAsset` سيقوم بتضمين بصمة محتوى تتغير اللقطة مع كل تعديل للكود.)

## الخطوة 8: اختبار قواعد الحوكمة باستخدام Aspects و Annotations

بالإضافة إلى الموارد الفردية، غالبًا ما تريد سياسة عبر التطبيق بالكامل — على سبيل المثال، "يجب أن يكون لكل S3 bucket خاصية تعقيب الإصدارات (versioning)." يقوم الـ Aspect بزيارة كل construct في الشجرة؛ ونحن نرفق خطأ في وقت التصنيع (synthesis-time) إذا انتهك الـ bucket القاعدة. أنشئ `lib/require-versioning.ts`:

```TypeScript
import { IAspect, Annotations from 'aws-cdk-lib';
import { IConstruct from 'constructs';
import { CfnBucket from 'aws-cdk-lib/aws-s3';

/** Fails validation if any S3 bucket has versioning disabled. */
export class RequireBucketVersioning implements IAspect {
public visit(node: IConstruct): void {
if (node instanceof CfnBucket) {
const versioning = node.versioningConfiguration as
| { status?: string | undefined;
if (!versioning || versioning.status !== 'Enabled') {
Annotations.of(node).addError(
'S3 buckets must have versioning enabled (data-retention policy).',
);
}
}
}
}
```

يسمح لك التحقق من `Annotations` API باختبار أن الـ Aspect يعمل بشكل صحيح. أنشئ `test/governance.test.ts`:

```TypeScript
import { App, Stack, Aspects from 'aws-cdk-lib';
import { Annotations, Match from 'aws-cdk-lib/assertions';
import { Bucket from 'aws-cdk-lib/aws-s3';
import { IngestPipeline from '../lib/ingest-pipeline';
import { RequireBucketVersioning from '../lib/require-versioning';

test('flags a bucket created without versioning', () => {
  const app = new App();
  const stack = new Stack(app, 'BadStack');
  new Bucket(stack, 'Unversioned'); // versioning off by default
  Aspects.of(stack).add(new RequireBucketVersioning());

  Annotations.fromStack(stack).hasError(
    '*',
    Match.stringLikeRegexp('versioning enabled'),
  );
});

test('passes a compliant pipeline with no errors', () => {
  const app = new App();
  const stack = new Stack(app, 'GoodStack');
  new IngestPipeline(stack, 'Ingest'); // bucket is versioned
  Aspects.of(stack).add(new RequireBucketVersioning());

  Annotations.fromStack(stack).hasNoError('*', Match.anyValue());
});
```

يكشف `Annotations.fromStack(stack)` عن `hasError`، و `hasNoError`، و `hasWarning`، و `findError`[^annotations]. الوسيط الأول هو مسار الـ construct (حيث يطابق `'*'` أي مسار)، والثاني هو مطابق للرسالة. يحول هذا النمط قواعد الامتثال الخاصة بك إلى اختبارات، بحيث يفشل أي تغيير مستقبلي يعطل تعقيب الإصدارات في CI بدلاً من شحنه.

## الخطوة 9: تشغيل مجموعة الاختبارات في CI بدون حساب AWS

نظرًا لأن التصنيع (synthesis) محلي، فإن المجموعة الكاملة تعمل على مشغل CI عادي بدون أوراق اعتماد سحابية. أضف `.GitHub/workflows/test.yml`:

```yaml
name: cdk-tests
on:
  push:
  pull_request:
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm test
```

هذا هو خط الأنابيب (pipeline) بالكامل. بمجرد اجتياز هذه الاختبارات، يمكنك توصيل وظيفة نشر تفترض دورًا باستخدام OIDC — راجع [دليل نشر AWS بدون مفاتيح باستخدام GitHub Actions OIDC](https://nerdleveltech.com/GitHub-actions-oidc-aws-terraform-keyless-deploys-tutorial) — مع العلم أنه تم التحقق من القالب أولاً. إذا قمت أيضًا بتصدير التتبعات (traces) من التطبيق المنشور، فإن [دليل تتبع Node.js باستخدام OpenTelemetry Collector](https://nerdleveltech.com/opentelemetry-collector-nodejs-tracing-tutorial) يتناسب جيدًا مع بوابة CI هذه.

## التحقق

قم بتشغيل المجموعة الكاملة:

```bash
npm test
```

المخرجات المتوقعة (ستختلف الإصدارات والتوقيت):

```text
PASS test/governance.test.ts
PASS test/snapshot.test.ts
PASS test/ingest-pipeline.test.ts

Test Suites: 3 passed, 3 total
Tests:       10 passed, 10 total
Snapshots:   1 written, 1 total
```

لتأكيد أن التصنيع نفسه يعمل بدون أوراق اعتماد، قم بإلغاء تعيين أي متغيرات AWS وقم بتصنيع التطبيق:

```bash
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_PROFILE
npx cdk synth
```

يخرج بـ `0` ويكتب `cdk.out/AppStack.template.json` الذي يحتوي على S3 bucket، وسياسة الـ bucket، ودور وسياسة IAM، ودالة Lambda — وهو دليل على أن نفس القالب الذي تحققت منه اختباراتك هو الذي سيقوم CDK بنشره.

## الأخطاء الشائعة

**`ts-jest[config] TS151002: Using hybrid module kind ... isolatedModules`.** يستخدم ملف `tsconfig.json` الخاص بالهيكل `module: "NodeNext"`. اضبط `"isolatedModules": true` في `compilerOptions` (الخطوة 1). التحذير بخلاف ذلك غير ضار ولكنه مزعج.

**فشل اختبار برسالة "Template has N resources ... but none match as expected."** هذه هي رسالة الفشل الدقيقة العادية. يطبع CDK أقرب مورد مطابق ويحدد السطر المخالف، على سبيل المثال `!! Expected 25 but received 30` في `Timeout`. اقرأ المورد المطبوع، وأصلح إما الـ construct أو التحقق، وأعد التشغيل.

**كسر اختبار اللقطة بعد `npm update`.** يمكن أن يؤدي ترقية `aws-cdk-lib` بشكل مشروع إلى تغيير المخرجات المصنعة. تأكد من أن الفرق ناتج فقط عن المكتبة، ثم قم بتحديث الأساس باستخدام `npm test -- -u`. هذا هو بالضبط السبب في أن اللقطات لا ينبغي أن تكون شبكة اختبار التراجع الخاصة بك (الخطوة 7).

**تحقق `Capture` يرمي خطأ "is not a function" أو لا يطابق أبدًا.** استدعِ `.asObject()` أو `.asString()` فقط بعد تشغيل استدعاء `hasResourceProperties` الذي يملأ الالتقاط، وتحقق من النتيجة باستخدام مطابقات Jest — وليس باستخدام مطابق CDK `Match.*`.

**`A jest worker process was terminated ... SIGKILL`.** في المشغلات ذات الذاكرة المحدودة، يمكن أن يؤدي تصنيع CDK بالإضافة إلى مشغلات Jest المتوازية إلى استنفاد ذاكرة الوصول العشوائي (RAM). قم بالتشغيل باستخدام `npm test -- --maxWorkers=1` (أو `--runInBand`). المشغلات الافتراضية جيدة على جهاز عادي.

**`cdk synth` يطبع "NN feature flags are not configured."** هذا إشعار معلوماتي من CLI في إصدارات CDK الحديثة، وليس خطأ؛ التصنيع لا يزال ينجح. لا يؤثر ذلك على الاختبارات، التي تستدعي `Template.fromStack` مباشرة.

## الخطوات التالية

لديك الآن مجموعة اختبارات CDK تثبت إعدادات الموارد، وتتحقق من القيم الناتجة، وتثبت عمليات إعادة الهيكلة (refactors) باستخدام لقطة شاشة (snapshot)، وتفرض قواعد الحوكمة — كل ذلك قبل نشر أي شيء. من هنا، يمكنك إضافة تأكيدات (assertions) لسياسات IAM ذات الامتيازات الأقل، واختبار عدة Stacks في تطبيق واحد، ودمج هذه الاختبارات في بوابة النشر الموضحة في [دليل تعليمات GitHub Actions OIDC للنشر بدون مفاتيح](https://nerdleveltech.com/GitHub-actions-oidc-aws-terraform-keyless-deploys-tutorial).

## قراءات إضافية

- AWS — *اختبار تطبيقات AWS CDK*[^testing]
- AWS — *مرجع وحدة aws-cdk-lib.assertions*[^assertions]

[^jest]: Jest on npm (30.4.2). <https://www.npmjs.com/package/jest>

[^lib]: aws-cdk-lib on npm (2.260.0 latest as of 18 June 2026). <https://www.npmjs.com/package/aws-cdk-lib>

[^scaffold]: Scaffold generated by the aws-cdk CLI `cdk init app --language TypeScript` (aws-cdk 2.1128.0). <https://www.npmjs.com/package/aws-cdk>

[^autoclean]: Generated into `jest.config.js` by `cdk init`; implemented in the aws-cdk-lib package as `testhelpers/jest-autoclean` (registers `afterAll(CloudAssembly.cleanupTemporaryDirectories)`). <https://www.npmjs.com/package/aws-cdk-lib>

[^runtimes]: AWS — Lambda Node.js runtimes (nodejs22.x supported to Apr 2027; nodejs24.x available since Nov 2025). <https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html>

[^testing]: AWS — Test AWS CDK applications (fine-grained vs snapshot guidance). <https://docs.aws.amazon.com/cdk/v2/guide/testing.html>

[^assertions]: AWS — aws-cdk-lib.assertions module (Template, Match, Capture). <https://docs.aws.amazon.com/cdk/API/v2/docs/aws-cdk-lib.assertions-readme.html>

[^annotations]: AWS — assertions `Annotations` API (hasError / hasNoError / hasWarning / findError). <https://docs.aws.amazon.com/cdk/API/v2/docs/aws-cdk-lib.assertions-readme.html>
