Repository navigation
Schematics: Unit testing a rule that uses mergeWith/apply fails due to missing context #17205
Description
Activity
- addedfreq1: lowOnly reported by a handful of users who observe it rarelyOnly reported by a handful of users who observe it rarely
on Mar 13, 2020 Hi, I think the problem here is that the destination folder (libs) doesn't exist.
@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 AgiusHaving 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
callRulemethod in the test runner doesn't create a real Schematic, but just passes in an empty object:const context = this._engine.createContext({} as Schematic<{}, {}>, parentContext); Eventually that empty
schematicwends 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
descriptionis 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
callRulemethod would need to provide a way to override or extend that empty schematic object to make this work.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.
Addendum
In creating the test case I've realised that I'm not passing a
partialContexttocallRule, so it is definitely missing context, however, it is not at all clear what context is necessary formergeWithto 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
callRuleto unit test rules within a schematic fails when it comes to rules which returnmergeWith🔬 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:🔥 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