// file: password-verifier-time00.js
constmoment=require('moment');constSUNDAY=0,SATURDAY=6;constverifyPassword=(input,rules)=>{constdayOfWeek=moment().day();if([SATURDAY,SUNDAY].includes(dayOfWeek)){throwError("It's the weekend!");}//more code goes here...
//return list of errors found..
return[];};
constverifyPassword=(input,rules,getDayFn)=>{constdayOfWeek=getDayFn();if([SATURDAY,SUNDAY].includes(dayOfWeek)){throwError("It's the weekend!");}//more code goes here...
//return list of errors found..
return[];};
// file: password-verifier-time00-modular.js
constoriginalDependencies={moment:require(‘moment’),};letdependencies={...originalDependencies};constinject=(fakes)=>{Object.assign(dependencies,fakes);returnfunctionreset(){dependencies={...originalDependencies};}};constSUNDAY=0;constSATURDAY=6;constverifyPassword=(input,rules)=>{constdayOfWeek=dependencies.moment().day();if([SATURDAY,SUNDAY].includes(dayOfWeek)){throwError("It's the weekend!");}// more code goes here...
// return list of errors found..
return[];};module.exports={SATURDAY,verifyPassword,inject};
const{inject,verifyPassword,SATURDAY}=require('./password-verifier-time00-modular');constinjectDate=(newDay)=>{constreset=inject({moment:function(){//we're faking the moment.js module's API here.
return{day:()=>newDay}}});returnreset;};describe('verifyPassword',()=>{describe('when its the weekend',()=>{it('throws an error',()=>{constreset=injectDate(SATURDAY);expect(()=>verifyPassword('any input')).toThrow("It's the weekend!");reset();});});});
functionFakeTimeProvider(fakeDay){this.getDay=function(){returnfakeDay;}}describe('verifier',()=>{test('class constructor: on weekends, throws exception',()=>{constverifier=newPasswordVerifier([],newFakeTimeProvider(SUNDAY));expect(()=>verifier.verify('anything')).toThrow("It's the weekend!");});});
exportclassPasswordVerifier{private_timeProvider:TimeProviderInterface;constructor(rules:any[],timeProvider:TimeProviderInterface){this._timeProvider=timeProvider;}verify(input:string):string[]{constisWeekened=[SUNDAY,SATURDAY].filter(x=>x===this._timeProvider.getDay()).length>0;if(isWeekened){thrownewError("It's the weekend!")}// more logic goes here
return[];}}
這樣的寫法就很類似 Java 或是 C# ,而測試的寫法看起來也很雷同了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
classFakeTimeProviderimplementsTimeProviderInterface{fakeDay:number;getDay():number{returnthis.fakeDay;}}describe('password verifier with interfaces',()=>{test('on weekends, throws exceptions',()=>{conststubTimeProvider=newFakeTimeProvider();stubTimeProvider.fakeDay=SUNDAY;constverifier=newPasswordVerifier([],stubTimeProvider);expect(()=>verifier.verify('anything')).toThrow("It's the weekend!");});});
Object as parameter aka duck typing,在 javascript 可以利用語言的特性,實做相同簽章的方法來替換原先的實做內容,進而把該相依解耦
Common interface as parameter 這個手法類型常見的物件導向類型的語言作法,好處是可以在編譯時期就抓到可能的錯誤,但你會需要像是 typescript 的支援做到。
其實本書提到的避免使用 modular injection 作法,應該是說若沒有使用測試套件的功能那就會讓更動幅度變大,這樣的話就很不建議使用這種方式解耦,你可以重構程式變成 Object as parameter 或是 Common interface as parameter 這樣可以讓你的程式碼跟測試可以不會因為第三方模組綁的太深,導致可讀性跟強健性變低。