1- import ma = require ( './mock-answer' ) ;
2- import mockery = require ( 'mockery' ) ;
1+ import { TaskLibAnswers } from './mock-answer' ;
2+ import { SinonSandbox , createSandbox } from 'sinon' ;
33import im = require( './internal' ) ;
4+ import * as taskLib from './task' ;
45
56export class TaskMockRunner {
67 constructor ( taskPath : string ) {
78 this . _taskPath = taskPath ;
9+ this . _sandbox = createSandbox ( ) ;
810 }
911
1012 _taskPath : string ;
11- _answers : ma . TaskLibAnswers | undefined ;
13+ _answers : TaskLibAnswers | undefined ;
1214 _exports : { [ key : string ] : any } = { } ;
1315 _moduleCount : number = 0 ;
16+ private _sandbox : SinonSandbox ;
1417
1518 public setInput ( name : string , val : string ) {
1619 let key : string = im . _getVariableKey ( name ) ;
@@ -32,10 +35,43 @@ export class TaskMockRunner {
3235 *
3336 * @param answers Answers to be returned when the task lib functions are called.
3437 */
35- public setAnswers ( answers : ma . TaskLibAnswers ) {
38+ public setAnswers ( answers : TaskLibAnswers ) {
3639 this . _answers = answers ;
3740 }
3841
42+ checkModuleName ( modName : any ) : boolean {
43+ if ( typeof modName !== 'string' ) {
44+ return false ;
45+ }
46+
47+ if ( modName . includes ( '.' ) ) {
48+ console . log ( `WARNING: ${ modName } is a local module. Cannot require it from task-lib. Please pass an already imported module.` ) ;
49+ return false ;
50+ }
51+
52+ return true ;
53+ }
54+
55+
56+ checkIsMockable ( newModule , methodName , oldModule ) {
57+ const method = newModule [ methodName ] ;
58+
59+ if ( ! newModule . hasOwnProperty ( methodName ) || typeof method === 'undefined' ) {
60+ return false ;
61+ }
62+
63+ if ( typeof method !== 'function' ) {
64+ console . log ( `WARNING: ${ methodName } of ${ newModule } is not a function. There is no option to replace getter/setter in this implementation. You can consider changing it.` ) ;
65+ return false ;
66+ }
67+
68+ const descriptor = Object . getOwnPropertyDescriptor ( oldModule , methodName ) ;
69+
70+ return descriptor && descriptor . writable !== false ;
71+ }
72+
73+
74+
3975 /**
4076 * Register a mock module. When require() is called for the module name,
4177 * the mock implementation will be returned instead.
@@ -44,9 +80,22 @@ export class TaskMockRunner {
4480 * @param val Mock implementation of the module.
4581 * @returns void
4682 */
47- public registerMock ( modName : string , mod : any ) : void {
83+ public registerMock ( modName : any , mod : any ) : void {
4884 this . _moduleCount ++ ;
49- mockery . registerMock ( modName , mod ) ;
85+ let oldMod : object ;
86+
87+ if ( this . checkModuleName ( modName ) ) {
88+ oldMod = require ( modName ) ;
89+ } else {
90+ oldMod = modName ;
91+ }
92+
93+ for ( let method in oldMod ) {
94+ if ( this . checkIsMockable ( mod , method , oldMod ) ) {
95+ const replacement = mod [ method ] || oldMod [ method ] ;
96+ this . _sandbox . replace ( oldMod , method , replacement ) ;
97+ }
98+ }
5099 }
51100
52101 /**
@@ -69,11 +118,6 @@ export class TaskMockRunner {
69118 * @returns void
70119 */
71120 public run ( noMockTask ?: boolean ) : void {
72- // determine whether to enable mockery
73- if ( ! noMockTask || this . _moduleCount ) {
74- mockery . enable ( { warnOnUnregistered : false } ) ;
75- }
76-
77121 // answers and exports not compatible with "noMockTask" mode
78122 if ( noMockTask ) {
79123 if ( this . _answers || Object . keys ( this . _exports ) . length ) {
@@ -92,10 +136,21 @@ export class TaskMockRunner {
92136 tlm [ key ] = this . _exports [ key ] ;
93137 } ) ;
94138
95- mockery . registerMock ( 'azure-pipelines-task-lib/task' , tlm ) ;
139+
140+ var tlt = require ( 'azure-pipelines-task-lib/task' ) ;
141+ for ( let method in tlt ) {
142+ if ( tlm . hasOwnProperty ( method ) ) {
143+ this . _sandbox . replace ( tlt , method , tlm [ method ] ) ;
144+ }
145+ }
146+
96147 }
97148
98149 // run it
99150 require ( this . _taskPath ) ;
100151 }
152+
153+ public restore ( ) {
154+ this . _sandbox . restore ( ) ;
155+ }
101156}
0 commit comments