The Basics of Unit Testing in StencilJS | Elite Ionic
文章推薦指數: 80 %
A unit test is a chunk of code that is written and then executed to test that a single "unit" of your code behaves as expected. A unit test ...
JoshMoronyJuly30,201911minreadTestingstenciljestunite2etddOriginallypublishedJuly30,2019Automatedtestingsuchasunittestsandend-to-endtestsare,inmyview,oneofthebiggest"levelup"mechanicsavailabletodeveloperswhoarelookingtoimprovetheirskillset.Itisdifficultandmightrequirealotoftimetolearntogettothepointwhereyoufeelcompetentinwritingtestsforyourcode,andevenonceyouhavethebasicsdownthereisalwaysroomforimprovementtocreatebettertests-justasyoucancontinuetolearntowritebetterandbettercode.Thefunnythingaboutautomatedtestsisthattheyaresovaluable,butatthesametimenotatallrequired(andarethereforeoftenskipped).Youdon'tneedtestsassociatedwithyourcodetomakethemwork,andwritingtestswon'tdirectlyimpactthefunctionalityofyourapplicationatall.However,eventhoughtheteststhemselveswon'tchangeyourcodedirectlyorthefunctionalityofyourapplication,theywillfacilitatepositivechangesinotherways.Someofthekeybenefitsofinvestingtimeinautomatedtestsinclude:Documentation-unittestsaresetoutinsuchawaythattheyaccuratelydescribetheintendedfunctionalityoftheapplicationVerification-youcanhavegreaterconfidencethatyourapplication'scodeisbehavingthewayyouintendedRegressionTesting-whenyoumakechangestoyourapplicationyoucanbemoreconfidentthatyouhaven'tbrokenanythingelse,andifyouhavethereisagreaterchancethatyouwillfindoutaboutit,asyoucanrunthesametestsagainstthenewcodeCodeQuality-itisdifficulttowriteeffectivetestsforpoorlydesigned/organisedapplications,whereasitismucheasiertowritetestsforwell-designedcode.Naturally,writingautomatedtestsforyourapplicationswillforceyouintowritinggoodcodeSleep-you'llbelessofanervouswreckwhendeployingyourapplicationtotheappstorebecauseyouaregoingtohaveagreaterdegreeofconfidencethatyourcodeworks(andthesamegoesforwhenyouareupdatingyourapplication)Inthistutorial,wearegoingtofocusonthebasicsofwritingunittestsforStencilJScomponents.ThistutorialwillhaveanemphasisonusingIonicwithStencilJS,butthesamebasicconceptswillapplyregardlessofwhetheryouareusingIonicornot.IfyouwouldlikeaquickpreviewofwhatcreatingandrunningunittestsinaStencilJSapplicationlookslike,youcancheckoutthevideoIrecordedtoaccompanythistutorial:CreatingUnitTestsforStencilJSComponents.Mostofthedetailsareinthiswrittentutorial,butthevideoshouldhelptogiveyouabitofcontext.OutlineSourcecodeKeyPrinciplesExecutingUnitTestsinanIonic/StencilJSApplicationWritingOurOwnUnitTestsSummaryKeyPrinciplesBeforewegetintoworkingwithsomeunittests,weneedtocoverafewbasicideas.StencilJSusesJestforunittests,whichisagenericJavaScripttestingframework.IfyouarealreadyfamiliarwithJasminethenyouwillalreadyunderstandmostofwhatisrequiredforwritingtestswithJest,becausetheAPIisextremelysimilar.IhavealreadywrittenmanyarticlesoncreatingunittestswithJasmine,soifyouneedabitofanintroductiontothebasicideasIwouldrecommendreadingthisarticlefirst:HowtoUnitTestanIonic/AngularApplication-ofcourse,weareusingJestnotJasmine(asanIonic/Angularapplicationuses)butthebasicconceptsarealmostidentical.Althoughthearticleabovegoesintomoredetail,I'llalsobreakdownthebasicconceptshere.Thebasicstructureforaunittestmightlooksomethinglikethis:describe('MyService',()=>{
it('shouldcorrectlyaddnumbers',()=>{
expect(1+1).toBe(2);
});
});Jestprovidesthedescribe,it,andexpectmethodsthatweareusingabovetocreatethetest.Inthiscase,wearecreatingatestthatchecksthatnumbersareaddedtogethercorrectly.Thisisjustanexample,andsincewearejustdirectlyexecuting1+1(ratherthantestingsomeofouractualcodethathasthetaskofdoingthat)wearen'treallyachievinganythingbecausethisisalwaysgoingtowork.describe()definesatestsuite(e.g.a"collection")oftests(or"specs")it()definesaspecifictestor"spec",anditlivesinsideofasuite(describe()).Thisiswhatdefinestheexpectedbehaviourofthecodeyouaretesting,e.g."itshoulddothis","itshoulddothat"expect()definestheexpectedresultofatestandlivesinsideofit()Aunittestisachunkofcodethatiswrittenandthenexecutedtotestthatasingle"unit"ofyourcodebehavesasexpected.Aunittestmighttestthatamethodreturnstheexpectedvalueforagiveninput,orperhapsthataparticularmethodiscalledwhenthecomponentisinitialised.Itisimportantthatunittestsaresmallandisolated,theyshouldonlytestoneveryspecificthing.Forexample,weshouldn'tcreateasingle"thecomponentworks"testthatexecutesabunchofdifferentexpectstatementstotestfordifferentthings.Weshouldcreatemanysmallindividualunitteststhateachhavetheresponsibilityoftestingonesmallunitofcode.Therearemanyconceptsandprinciplestolearnwhenitcomestocreatingautomatedtests,andyoucouldliterallyreadentirebooksjustonthecoreconceptsoftestingingeneral(withoutgettingintospecificsofaparticularframeworkortestrunner).However,Ithinkthatoneofthemostimportantconceptstokeepinmindis:Arrange,Act,AssertorAAA.Allofyourtestswillfollowthesamebasicstructureof:Arrange-gettheenvironment/dependencies/componentssetupinthestaterequiredforthetestAct-runcodeinthetestthatwillexecutethebehaviouryouaretestingAssert-makeastatementofwhattheexpectedresultwas(whichwilldeterminewhetherornotthetestpassed)Wewilllookatexamplesofthisthroughoutthetutorial(especiallywhenwegetintowritingourownunittests).ExecutingUnitTestsinanIonic/StencilJSApplicationLet'sstartwithrunningsomeunittestsandtakingalookattheoutput.Bydefault,anewlygeneratedIonic/StencilJSapplicationwillhaveafewunittestsandend-to-endtestsalreadycreatedforthedefaultfunctionalityprovided.Thisisgreatbecausewecantakealookatthoseteststogetageneralideaofwhattheylooklike,andalsoexecutetheteststoseewhathappens.First,youshouldgenerateanewionic-pwaapplicationwiththefollowingcommand:npminitstencilOnceyouhavetheapplicationgenerated,andyouhavemadeityourcurrentworkingdirectory,youshouldcreateaninitialbuildofyourapplicationwith:npmrunbuildThentoexecutetheteststhatalreadyexistintheapplicationyouwilljustneedtorun:npmtestThefirsttimeyourunthis,itwillinstallalloftherequireddependenciestorunthetests.Thismighttakeawhile,butitwon'ttakethislongeverytimeyourunthiscommand.Thedefaulttestoutputforanuntouchedblankionic-pwaapplicationwilllooklikethis(Ihavetrimmedthisdownalittleforspace):PASSsrc/components/app-profile/app-profile.spec.ts
PASSsrc/components/app-root/app-root.spec.ts
PASSsrc/components/app-home/app-home.e2e.ts
PASSsrc/components/app-profile/app-profile.e2e.ts
Console
PASSsrc/components/app-root/app-root.e2e.ts
Console
PASSsrc/components/app-home/app-home.spec.ts
TestSuites:6passed,6total
Tests:13passed,13total
Snapshots:0total
Time:3.208s
Ranalltestsuites.Wecanseethatthereare13testsintotalacrossthethreedefaultcomponents,andallofthemhaveexecutedsuccessfully.Let'stakeacloserlookatwhatthesetestsareactuallydoingbyopeningthesrc/components/app-home/app-home.spec.tsfile:import{AppHome}from'./app-home';
describe('app',()=>{
it('builds',()=>{
expect(newAppHome()).toBeTruthy();
});
});Thislookssimilartotheexampletestthatwelookedatbefore,exceptnowweareactuallytestingrealcode.WeimporttheAppHomecomponentintothetestfile,andthenwehaveatestthatcreatesanewinstanceofAppHomeandchecksthatitis"truthy".Thisdoesn'tmeanthattheresultneedstobetruebutthattheresulthasa"true"valuesuchthatitisanobjectorastringoranumber,ratherthanbeingnullorundefinedwhichwouldbe"falsy".ThistestwillensurethatAppHomecanbesuccessfullyinstantiated.Thisisabasictestthatyoucouldaddtoanyofyourcomponents.Nowlet'shaveabitoffunbymakingitfailbychangingtoBeTruthytotoBeFalsy:import{AppHome}from'./app-home';
describe('app',()=>{
it('builds',()=>{
expect(newAppHome()).toBeFalsy();
});
});Ifweexecutethetestsnow,wewillgetafailure:FAILsrc/components/app-home/app-home.spec.ts
●app›builds
expect(received).toBeFalsy()
Received:{}
3|describe("app",()=>{
4|it("builds",()=>{
>5|expect(newAppHome()).toBeFalsy();
|^
6|});
7|});
8|
atObject.it(src/components/app-home/app-home.spec.ts:5:27)
TestSuites:1failed,5passed,6total
Tests:1failed,12passed,13total
Snapshots:0total
Time:3.56s
Ranalltestsuites.Wecanseethatweareexpectingthereceivedvaluetobe"falsy",butitisa"truthy"valueandsothetestfails.Thisshouldhighlightsomeoftheusefulnessofunittests.Whenourcodeisn'tdoingwhatweexpectitto,wecanseeexactlywhereitisfailingandwhy(assumingthetestsaredefinedwell).Thisisoneofthereasonstodesignsmall/isolatedunittests,aswhenafailureoccursitwillbemoreobviouswhatcausedthefailure.NOTE:RemembertochangethetestbacktotoBeTruthysothatitdoesn'tcontinuetofailNowlet'stakealookattheunittestsfortheprofilepagedefinedinsrc/components/app-profile/app-profile.spec.tswhicharealittlemoreinteresting:import{AppProfile}from'./app-profile';
describe('app-profile',()=>{
it('builds',()=>{
expect(newAppProfile()).toBeTruthy();
});
describe('normalization',()=>{
it('returnsablankstringifthenameisundefined',()=>{
constcomponent=newAppProfile();
expect(component.formattedName()).toEqual('');
});
it('capitalizesthefirstletter',()=>{
constcomponent=newAppProfile();
component.name='quincy';
expect(component.formattedName()).toEqual('Quincy');
});
it('lower-casesthefollowingletters',()=>{
constcomponent=newAppProfile();
component.name='JOSEPH';
expect(component.formattedName()).toEqual('Joseph');
});
it('handlessingleletternames',()=>{
constcomponent=newAppProfile();
component.name='q';
expect(component.formattedName()).toEqual('Q');
});
});
});Westillhavethebasic"builds"test,butwealsohavesomespecifictestsrelatedtothefunctionalityofthisparticularcomponent.Theprofilepagehassomefunctionalitybuiltintothathandlesformattingtheuser'sname.Thesetestscoverthatfunctionality.Noticethatwedon'tjusthaveasingle"itformatsthenamecorrectly"test.Theprocessforformattingthenameinvolvesseveraldifferentthings,sothesearegoodunittestsinthattheyareindividuallytestingforeach"unit"offunctionalityofthenameformatting.Generallyspeaking,themoreunittestsyouhaveandthemoreisolatedtheyarethebetter,butespeciallyasabeginnertrynottoobsesstoomuchovergettingthings"perfect".Havingabloatedunittestisstillmuchbetterthannottestingatall.Theonethingyoureallydohavetowatchoutforisthatyourtestsareactuallytestingwhatyouthinktheyare.Youmightdesignatestinawaywhereitwillalwayspass,nomatterwhatcodeyouhaveimplemented.Thisisbad,becauseitwillmakeyouthinkyourcodeisworkingasexpectedwhenitisnot.TheTestDrivenDevelopmentapproach,whichwewillbrieflydiscussinamoment,canhelpalleviatethis.WritingOurOwnUnitTestsTofinishthingsoff,let'sactuallybuildoutsometestsofourowninanewcomponent.Itissimpleenoughtolookatsomepre-definedtestandhaveabasicunderstandingofwhatisbeingtested,butwhenyoucometowriteyourowntestsitcanbereallydifficult.Wewilllookintocreatinganadditionalapp-detailcomponent,whichwillhavethepurposeofexceptinganidasapropfromthepreviouspage,andthenusingthatidtofetchanitemfromaservice.WearegoingtofollowalooseTestDrivenDevelopment(TDD)approachhere.Thisisawholenewtopicinitself.Itisastructuredandstrictprocessforwritingtests,butIthinkitcanactuallyhelpbeginnersgetintotesting.Sincethereisastrictprocesstofollow,itbecomeseasiertodeterminewhatkindoftestsyoushouldbewritingandwhenyoushouldwritethem.Iwon'tgetintoabigspielaboutwhatTDDisinthistutorial,butforsomecontext,Iwouldrecommendreadingoneofmypreviousarticlesonthetopic:TestDrivenDevelopmentinIonic:AnIntroductiontoTDD.ThebasicideabehindTestDrivenDevelopmentisthatyouwritethetestsfirst.Thismeansthatyouwillbeattemptingtotestcodethatdoesnotevenexistyet.Youwillfollowthisbasicprocess:WriteatestthatteststhefunctionalityyouwanttoimplementRunthetest(itwillfail)WritethefunctionalitytosatisfythetestRunthetest(itwillhopefullypass-ifnot,fixthefunctionalityorthetestifrequired)Thekeybenefittobeginnerswiththisprocessisthatyouknowwhattocreatetestsfor,andyoucanbereasonablyconfidentthetestiscorrectifitfailsinitiallybutafterimplementingthefunctionalitythetestworks.Itisanimportantsteptomakesurethetestfailsfirst.Let'sgetstartedbydefiningthefilesnecessaryforournewcomponent.Createthefollowingfiles:src/components/app-detail/app-detail.tsxsrc/components/app-detail/app-detail.csssrc/components/app-detail/app-detail.spec.tsBeforeweimplementanycodeatallfortheapp-detailcomponent,wearegoingtocreateatestinthespecfile:import{AppDetail}from'./app-detail';
describe('app',()=>{
it('builds',()=>{
expect(newAppDetail()).toBeTruthy();
});
});Ifwetrytorunthis,wearegoingtogetafailure:FAILsrc/components/app-detail/app-detail.spec.ts
●app›builds
TypeError:app_detail_1.AppDetailisnotaconstructor
3|describe("app",()=>{
4|it("builds",()=>{
>5|expect(newAppDetail()).toBeTruthy();
|^
6|});
7|});
8|
atObject.it(src/components/app-detail/app-detail.spec.ts:5:12)
PASSsrc/components/app-root/app-root.spec.ts
TestSuites:1failed,6passed,7total
Tests:1failed,13passed,14total
Snapshots:0total
Time:3.363s
Ranalltestsuites.Thismakessense,becausewehaven'tevencreatedtheAppDetailcomponentyet.Nowlet'sdefinethecomponenttosatisfythetest:import{Component,h}from'@stencil/core';
@Component({
tag:'app-home',
styleUrl:'app-home.css',
})
exportclassAppHome{
render(){
return[
延伸文章資訊
- 1Unit Testing Stencil One - Tally Barak
This is nice, but it is not so much of testing. The main goal of unit testing is to verify that t...
- 2Stencil Test - OpenGL Wiki
The Stencil Test is a per-sample operation performed after the Fragment Shader. The fragment's st...
- 3Unit Testing Stencil Components - Ionic Blog
We have added a unit testing framework to Stencil. Stencil is a tool for building modern Web Comp...
- 4Unit Testing - Stencil.js
Stencil makes it easy to unit test components and app utility functions using Jest. Unit tests va...
- 5Examples of how to test Stencil components - GitHub
Examples of how to test Stencil components. Contribute to jagreehal/stencil-how-to-test-component...