GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Schematics: Unit testing a rule that uses mergeWith/apply fails due to missing context · Issue #17205 · angular/angular-cli

Schematics: Unit testing a rule that uses mergeWith/apply fails due to missing context #17205

New issue
Copy link
New issue
Copy link
Open
Open
Schematics: Unit testing a rule that uses mergeWith/apply fails due to missing context#17205
Copy link
Labels
area: @angular-devkit/schematicsfreq1: lowOnly reported by a handful of users who observe it rarelyOnly reported by a handful of users who observe it rarelyseverity3: brokentype: bug/fix
Milestone
Backlog

Description

@catfireparty
catfireparty
opened on Mar 12, 2020
Issue body actions

Addendum

In creating the test case I've realised that I'm not passing a partialContext to callRule, so it is definitely missing context, however, it is not at all clear what context is necessary for mergeWith to succeed and the error does not indicate that context is actually missing.

Happy for this to be marked as a question instead. Guidance on how to provide the context and what context to provide would be amazingly useful.

🐞 Bug report

Description

Using callRule to unit test rules within a schematic fails when it comes to rules which return mergeWith

🔬 Minimal Reproduction

See: https://github.com/jdpearce/schematics-apply-problem

Given a rule which applies template files to the tree, this unit test will fail with TypeError: Cannot read property 'path' of undefined:

describe('applyTemplateFiles Rule', () => {
  let tree: UnitTestTree;

  beforeEach(async () => {
    tree = new UnitTestTree(Tree.empty());
  });

  it('should apply the files to the tree', async () => {
    const schema = {};

    tree = (await callRule(applyTemplateFiles(schema), tree)) as UnitTestTree;

    expect(tree.files).toEqual(jasmine.arrayContaining(['/libs/destination/test.txt']));
  });
});

🔥 Exception or Error

Failures:
1) applyTemplateFiles Rule should apply the files to the tree
  Message:
    TypeError: Cannot read property 'path' of undefined
  Stack:
        at <Jasmine>
        at ./apply-problem/node_modules/@angular-devkit/schematics/tools/file-system-engine-host-base.js:216:96
        at ./tmp/apply-problem/node_modules/@angular-devkit/schematics/src/rules/url.js:13:73
        at Object.callSource (./tmp/apply-problem/node_modules/@angular-devkit/schematics/src/rules/call.js:55:20)
        at ./tmp/apply-problem/node_modules/@angular-devkit/schematics/src/rules/base.js:45:60
        at Object.callSource (./tmp/apply-problem/node_modules/@angular-devkit/schematics/src/rules/call.js:55:20)
        at ./tmp/apply-problem/node_modules/@angular-devkit/schematics/src/rules/base.js:53:23
        at MergeMapSubscriber.project (./tmp/apply-problem/node_modules/@angular-devkit/schematics/src/rules/call.js:74:24)
        at MergeMapSubscriber._tryNext (./tmp/apply-problem/node_modules/rxjs/internal/operators/mergeMap.js:69:27)
        at MergeMapSubscriber._next (./tmp/apply-problem/node_modules/rxjs/internal/operators/mergeMap.js:59:18)
        at MergeMapSubscriber.Subscriber.next (./tmp/apply-problem/node_modules/rxjs/internal/Subscriber.js:66:18)

2 specs, 1 failure

🌍 Your Environment

"@angular-devkit/core": "^9.0.6",
"@angular-devkit/schematics": "^9.0.6",
"@types/jasmine": "^3.3.9",
"@types/node": "^8.0.31",
"jasmine": "^3.3.1",
"typescript": "~3.5.3"

Activity

  1. alan-agius4
    added
    freq1: lowOnly reported by a handful of users who observe it rarelyOnly reported by a handful of users who observe it rarely
    severity3: broken
    type: bug/fix
    area: @angular-devkit/schematics
    on Mar 13, 2020
  2. ngbot
    added this to the Backlog milestone on Mar 13, 2020
  3. alan-agius4 commented on Mar 13, 2020

    @alan-agius4
    alan-agius4
    on Mar 13, 2020
    Collaborator
    More actions

    Hi, I think the problem here is that the destination folder (libs) doesn't exist.

  4. catfireparty commented on Mar 16, 2020

    @catfireparty
    catfireparty
    on Mar 16, 2020
    Author
    More actions

    @alan-agius4 If you check the linked repo, running the schematic fully succeeds. The problem is in the missing context when using callRule. It's not clear what that missing context should be in the case of a unit test.

    Reacted by Alan Agius
  5. tvsbrent commented on Jun 1, 2021

    @tvsbrent
    tvsbrent
    on Jun 1, 2021
    More actions

    Having hit the same issue with a rule I'm working on and from digging in, it looks like it is due to the fact that the callRule method in the test runner doesn't create a real Schematic, but just passes in an empty object:

    angular-cli/packages/angular_devkit/schematics/testing/schematic-test-runner.ts

    Line 113 in 37a06a7

    const context = this._engine.createContext({} as Schematic<{}, {}>, parentContext);

    Eventually that empty schematic wends its way here:

    angular-cli/packages/angular_devkit/schematics/src/engine/engine.ts

    Lines 254 to 265 in 37a06a7

    let context: TypedSchematicContext<CollectionT, SchematicT> = {
    debug: (parent && parent.debug) || false,
    engine: this,
    logger:
    (parent && parent.logger && parent.logger.createChild(schematic.description.name)) ||
    new logging.NullLogger(),
    schematic,
    strategy:
    parent && parent.strategy !== undefined ? parent.strategy : this.defaultMergeStrategy,
    interactive,
    addTask,
    };

    Finally, when the file system engine tries to get the path from the schematic, it blows up, as description is not defined:

    angular-cli/packages/angular_devkit/schematics/tools/file-system-engine-host-base.ts

    Line 288 in c1512e4

    const fileDescription = context.schematic.description as { path?: string };

    I think the callRule method would need to provide a way to override or extend that empty schematic object to make this work.

  6. clusterb93 commented on Mar 30, 2022

    @clusterb93
    clusterb93
    on Mar 30, 2022
    More actions

    We had the same problem described below and the only solution that we found was to create a CustomSchematicTestRunner that extends the angular-devkit SchematicTestRunner. This custom class exposes a new method that allows to pass a test schematic and context as parameters :

    /**
     * Custom Schematic Test Runner
     */
    
    import { Observable, of as observableOf } from 'rxjs';
    import {SchematicTestRunner} from "@angular-devkit/schematics/testing";
    import {callRule, Rule, Schematic, SchematicContext, Tree} from "@angular-devkit/schematics";
    
    export class CustomSchematicTestRunner extends SchematicTestRunner{
        constructor(collectionName: string, collectionPath: string) {
            super(collectionName, collectionPath);
        }
    
        /**
         * Calls a schematics Rule using an isolated context and the schematics passed in as parameter
         * @param rule
         * @param tree
         * @param schematic
         * @param parentContext
         */
        callSchematicsRule(rule: Rule, tree: Tree, schematic: Schematic<{}, {}>,  parentContext?: Partial<SchematicContext>): Observable<Tree> {
            const context = this.engine.createContext(schematic, parentContext);
            return callRule(rule, observableOf(tree), context);
        }
    }
    

    And in the Rule's unit test the schematic and the context were setted in a beforeEach function as follows :

     schematicTest = {
                description : {
                    name: "Test schematic",
                    path: "src/init"
                }
            };
     testContext = {
                debug: true,
                engine: this,
                logger:
                    new logging.NullLogger(),
                schematicTest,
                strategy: MergeStrategy.Default,
            };
    

    And the final test looks like :

    it('should create a jenkinsfile', async () => {
            const tree = await schematicRunner
                .callSchematicsRule(createJenkinsfile(), appTree, schematicTest as Schematic<{}, {}>, testContext)
                .toPromise();
    
            expect(tree.exists("Jenkinsfile")).toBeTrue();
        });
    

    Hope this technique helps and let you unit test your schematic's Rules waiting for an official fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: @angular-devkit/schematicsfreq1: lowOnly reported by a handful of users who observe it rarelyOnly reported by a handful of users who observe it rarelyseverity3: brokentype: bug/fix

    Type

    No type

    Projects

    No projects

      Milestone

      • BacklogNo due date

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.