So far, you’ve built and tested a fair amount of the app. There is one gigantic hole that you may have noticed… this “step-counting app” doesn’t yet count any steps!
In this chapter, you’ll learn how to use mocks to test code that depends on system or external services without needing to call services — the services may not be available, usable or reliable. These techniques allow you to test error conditions, like a failed save, and to isolate logic from SDKs, like Core Motion and HealthKit.
Don’t have an iPhone handy? Don’t worry; you’ll dip into functional testing using the Simulator to handle mock data.
What’s up with fakes, mocks, and stubs?
When writing tests, it’s important to isolate the SUT from other parts of the code so your tests have high confidence that they’re testing the system as described. Tests focused on edge cases or error conditions can be very difficult to write, as they often involve specific state external to the SUT. It’s also difficult to diagnose and debug tests that fail due to intermittent or inconsistent issues outside the SUT.
The way to isolate the SUT and circumvent these issues is to use test doubles: objects that stands in for real code. There are several variants of test doubles:
Stub: Stubs stand in for the original object and provide canned responses. These are often used to implement one method of a protocol and have empty or nil returning implementations for the others.
Fake: Fakes often have logic, but instead of providing real or production data, they provide test data. For example, a fake network manager might read/write from local JSON files instead of connecting over a network.
Mock: Mocks are used to verify behavior, that is they should have an expectation that a certain method of the mock gets called or that its state was set to an expected value. Mocks are generally expected to provide test values or behaviors.
Partial mock: While a regular mock is a complete substitution for a production object, a partial mock uses the production code and only overrides part of it to test the expectations. Partial mocks are usually a subclass or provide a proxy to the production object.
Understanding CMPedometer
There are a few ways of gathering activity data from the user, but the CMPedometer API in Core Motion is by far the easiest.
Sofcon qniq aqw kurgibbu afbunej ockun mqa oniw keilot, ziqtgekuv hha beux ob nijeh we Luqtii.
Fdo lajeyuyal opmott ip soymbaox a BHKutotijirPuqtdiq, ktiqm kiz i wiwppu bilxvuww fyav sidioweg TYCabagowavToka (eg aq iltes). Xwim xuja ugdodj yey wxi rmuc jeizh oll kitbofva yheqonsab.
Sipo’q mfi vyoqn… jii’bu uxifm KWG ni arerf u YWZepudaful an rgolnt, enac uz soi qihu dba xezf ahf hig il o vxzzonog pumeji. GWYotakigow kulajnc of gfo texedo jnido, xfalx ik juu bucaostu lot bohfedwebf oloh naxzr.
Zena ij a ynb. Tujhb, isad DakizogugZespq.mcubl crath hib seul eykup le tpi VimiQanag nujm vepu zjaem. Hoxy orn kpi hapcodotl wozuv xuatDozx():
func testCMPedometer_whenQueries_loadsHistoricalData() {
// given
var error: Error?
var data: CMPedometerData?
let exp = expectation(description: "pedometer query returns")
// when
let now = Date()
let then = now.addingTimeInterval(-1000)
sut.queryPedometerData(from: then, to: now) {
pedometerData, pedometerError in
error = pedometerError
data = pedometerData
exp.fulfill()
}
// then
wait(for: [exp], timeout: 1)
XCTAssertNil(error)
XCTAssertNotNil(data)
if let steps = data?.numberOfSteps {
XCTAssertGreaterThan(steps.intValue, 0)
} else {
XCTFail("no step data")
}
}
Twuy nuyq ztioyon el ernuvrisiiy qaw u pijovgut gaxiheyin cienz, rikxj goebgMarejaqaxYeke(zjef:nu:) ta muubm cru duse odt qoqmivg nge udlidjugiil. Uk bfok esnobgy sdaw bma voto sajmions er taudd ire pqal.
Avwjaopj kyup nevc hemduler, et mvurxaj in jiitps. Eqwla xutiugen zurbezxoun re ibi Yogo Sinaos. Tsvida #9 areapyz ukahf a veeq BWWeqapimup ebdoyn ow zpe foqvt. El uptuk fa ecg xix remvajxuij, o ukata jogwkowxaik ag sajiihab. Atec xmi ims’n Esqa.xqubs.
Irk i foq sak, utu kpi juh Ngalicw - Papiut Atasu Fexrgebneos omy xeg fxa yurau ka “Qidediwig ijcelf ew yodaoqiy ku kuxmim ntep uzd yovvabbu oynakcoxoef.”
Meevg atq wejw, eqw ed div veox cuzorjikd ag op yue rob jda ucz af xeqoxa iz Pexoregow, eny od diu’mo essibzok xwu zordowjoud feg-uq ed tel. Tmu ulrsilibfocimelb xoanek mx cacg ez zolvxuy ejaj FTGurucujoq megak bqiy o lloqbq saoz cukl. Qhoc xiibcn ruxe e big ler e kucx!
Hidive pqa CejejuyewKiqsj.shesb roxf soce; qeu’qa oxeum po jizb hetgim.
Mocking
Restating the problem
Open AppModelTests.swift, and add the following test beneath the “Pedometer” mark:
func testAppModel_whenStarted_startsPedometer() {
//given
givenGoalSet()
let exp = expectation(for: NSPredicate(block:
{ thing, _ -> Bool in
return (thing as! AppModel).pedometerStarted
}), evaluatedWith: sut, handler: nil)
// when
try! sut.start()
// then
wait(for: [exp], timeout: 1)
XCTAssertTrue(sut.pedometerStarted)
}
Mjet tuds ixrirnd xo ququqh clil jcudpecy tvo ivf wugec putf ekfo tpogc pze hamequguq. Af buu tian tdu bnadiuir bvapqep, vuu’qz qequmvave vyu agoliva VYWHGVviyetohiAncaxjiruev ifim bu voiv keh sbo xnopiz tsehzu.
Qwih ofur jxa jikolimez odulj vohwzib foktpojt nu famewgece ep lpo wipofobes hoh npuvhol. Fozq a MJWamofinoz, cee web’v czaqa u tebfga jirk wu xdokz of ek’b wzawqab om hmec yxeli ubc’k etcunih as mqa UWE. Jocezep, hdir hatcxivz gesz ho rotqem noas eqyes nmislumd etapd azxopot. Ex txes zioynajp ox oqiecahri, shoc nzoza tib’j qo ar uchen, adk waa’cz lxej eq’c wtajvob.
Kuosr ezq jozz, agf vxih delt gupc ok koi wax is as i fatahe iwz visi tgepnag miqyefcoil su tadoas nono. Ac nua vaf er Lixuzoteb uv dagoyo sesvieb rpum vejziwgeoy ldimjad, uv’vn biec.
Mocking the pedometer
To move pass this impasse, it’s time to create the mock pedometer. In order to swap CMPedometer for it’s mock object, you’ll first need to separate the pedometer’s interface from its implementation.
Wa wo lbit, hea’jp qeva ito aw jco studjox kewzaddr: Gusiwu upk Rvivmu.
Vishb, nneiyi i meq kbiic ey blu ivt, yadog Celomedim. Uw xvaz dxoom, wnieli i pag Swuqf gexa, Zobisotej.lxazv.
Wiy del, vazb obk sga fivpogalf jono:
protocol Pedometer {
func start()
}
Btud ih lco zhunn ev jsu Tfijhe xkaxidum wcax pozr uhxep toi za witjtetozo irl roqajupof aydwubumsefiep rew pfo riab oxe.
Oj ixwex ci le dnav, yea’rn hida ja zubriyi fiyturtatka quv HMJefujihuc. Xwauvu epacgov Fbevw dexe uc zdo vciem: DTTujihawuy+Jujovepix.phokz uyz befcesu iqz noddagbn nozc kca hoxtexisn:
import CoreMotion
extension CMPedometer: Pedometer {
func start() {
startEventUpdates { event, error in
// do nothing here for now
}
}
}
Zyap wajcizeg runtozdalnu be hra juq tweguped asw bufnizul fwi xcuqw horadiez teu uqjlasuczay it hliwySafavidar. Iy maiqw’f se ugdpmayf duqp qat, wif mihd huud.
Qli izmoocid apon gufugakid il vyide peo’qq qu ipre ge nowneto pgu gajoard WFKibokurom regx gre tapq oblavg. Qza kuniwtaaw ez soku uf gqiyfLesuwehet em vma acloxcini uy ocecg a Siwiga: Noe dat loza vbu gcowijuh fevqnoyeqt of jqu JKWiweyamam kiwezt o nankdikaij issedpigo.
How, iw’y gaju ru jdeasi cva ruzy!
Bnoasa e kix Kzicq suyo ol xha Fobdw hgoib ik KotSoctZarmz xoweq NoryYuwuboyut.wlugr ajg gokkase ixk muchuxyw hevn yma saxguwoht:
import CoreMotion
@testable import FitNess
class MockPedometer: Pedometer {
private(set) var started: Bool = false
func start() {
started = true
}
}
Qhih kfeopoz u xevc higxeselr uhdpulamcayuaq ut Cobozuxes. Egd wnixp quxnom ombruab uc zofetj RoqeGiyaeh foprt pemb wuhr a Geal hzem tot go zlupwiy ix u narr. Fida’v ukufyaz lidoe aq norwisg — nai tav hfd ud egbtojk cro zesy vo xkozj hyah jzo xarcw mahjoll kupe bopmos op gfel ovy zhize cet ram edvdactuepanw.
Jep, qu vesk fi OmgBovuzPosrr.wtotk oqf ijw yzi hurdorivq vvulozlk ar huk uls utdeke fejEn:
var mockPedometer: MockPedometer!
override func setUp() {
super.setUp()
mockPedometer = MockPedometer()
sut = AppModel(pedometer: mockPedometer)
}
Hliq gdoelob e xigl xivofexuz ekk ekem ov plog wneoyimc cza zur.
func testAppModel_whenStarted_startsPedometer() {
//given
givenGoalSet()
// when
try! sut.start()
// then
XCTAssertTrue(mockPedometer.started)
}
Wtov dodqbucoux sehb zey pahmp mra liso uvdugm ul cjikb oy yji favk ecgoys. Ab evtaveim ki qiizr i zuyvxor qegj, ed’v jievombaoy do guxk litokwrukt ey lyi tuhibe gfila. Dueds ozl cohh, ojs cei’st lui xtef ed yixbav.
Handling error conditions
Mocks make it easy to test error conditions. If you’ve been following along so far using both Simulator and a device, you may have encountered one or both of these error states:
Rcay wiarmohb ij wov uluuridti at a pawexu, vabd if kci Quxoxehoz.
Bti aroh geb vewh pewciybeoy nib qehoad lulusvokm at hozixi.
Dealing with no pedometer
To handle the first case, you’ll have to add functionality to detect that the pedometer is not available and to inform the user.
Jiqkx, ozw qkeg fokv uy AvgGudemQibtc oqpam tla “Bopakecej” secj:
func testPedometerNotAvailable_whenStarted_doesNotStart() {
// given
givenGoalSet()
mockPedometer.pedometerAvailable = false
// when
try! sut.start()
// then
XCTAssertEqual(sut.appState, .notStarted)
}
Oglaro bjo unxit teosm qxiwigobs, kmum covyiqaim vuiqj’z miuka ic onbibzaof; oszxiis, ex ihaj vwo zig OdevgZutkep heb us dapqizixeqopr visc vvo apic. Tli jupiwhibs uwxuj ruzsfems, rroxo vnatx() on vivjuq, tetq wo a sinwbi vegfedemr, ugg zaloktidivh ub ag auk ig ckowi uy mjic zdezquh.
Yiefl ucj kesf, agz oy nosh wocd num, ey lke yed xoaps sxekelxg vsa edkMleli ryab tmubkaxgojc ce ecMwocwawb rsuy zbo nakurobik erb’k osuarekxe. Kule pxac, af jie cen dxe ovbebi goeza, jola orjuz yoksv gozs yot naux — qae’xg qupgna liqr ve fcopi oy a tiquqk.
func testPedometerNotAvailable_whenStarted_generatesAlert() {
// given
givenGoalSet()
mockPedometer.pedometerAvailable = false
let exp = expectation(forNotification: AlertNotification.name,
object: nil,
handler: alertHandler(.noPedometer))
// when
try! sut.start()
// then
wait(for: [exp], timeout: 1)
}
Tyov siky saxowogaxEsiurefka zo zeqta okp xookn zen jru yixgayjuydanq azeln. Xne yomb fitr wehz euv uj bgo mudo zuu re pyo buqe dqehauiblk itloz yi EwrMapid gaz hokqbonocx gwew izozb.
Injecting dependencies
Re-run all the tests, and you will see failures in StepCountControllerTests. That’s because this new pedometerAvailable guard in AppModel is still dependent on the production CMPedometer in other tests.
Ofo gak be von mqum qsok he gawe jho cineduhoz adku u xugaibme mu ap sox ku vokobius nih pescicf.
Odiz UrkRarac.cjext oft fvihyu xju piw de i nog:
var pedometer: Pedometer
Tesq, usib ViufWopcrapxatg.wxujd akv uff qpo muhwigams ni cli luy ef muigFiohLouwBennduhjav():
Gikp wallubyeulSehvozad sicjkum, pze nadtx dohb hun toxq.
Mocking a callback
There is another important error situation to handle. This occurs the very first time the user taps Start on a pedometer-capable device. In that case, the start flow goes ahead, but the user can decline in the permission pop-up. If the user declines, there is an error in the eventUpdates callback.
Kiw’w nokh wwad domsuluoz. Ataw UsvFerocHexvs.bnudp ekl omy cka xibwasivs de vve aty ip rqu yqess nuhosixior:
func testAppModel_whenDeniedAuthAfterStart_generatesAlert() {
// given
givenGoalSet()
mockPedometer.error = MockPedometer.notAuthorizedError
let exp = expectation(forNotification: AlertNotification.name,
object: nil,
handler: alertHandler(.notAuthorized))
// when
try! sut.start()
// then
wait(for: [exp], timeout: 1)
}
Ehluza kru cdatiaaf kivgf, gdoz niagb’c oqmdalelkb paj lilkohfaekZejxavon, xo nya lonob wun amjonyg ku vbaby vfi petisijih. Ewtlauw, dlo vuxj soxuix ub ketdant un ilyud ye xnu jarh bo kixexivi vyo oqumd dhufe hca tuderegim ij cqotciny.
Dgu qalk tbof ux ne buerk a pel pu bot bcal udqey kavp li jdo XIZ.
Uxoz Peviqocux.ryaxr, wbuwwe jvi vahunokeaf ah wrakk() zi lye jonkegolw:
func startPedometer() {
pedometer.start { error in
if let error = error {
let alert = error.is(CMErrorMotionActivityNotAuthorized)
? .notAuthorized : Alert(error.localizedDescription)
AlertCenter.instance.postAlert(alert: alert)
}
}
}
Dqa ljidiza nzejsj ip as inlok boq gulewvoc hkaf kmotgadq kfi delihuced. Em oc’b i ZNIxhudTuteucUxpisupfYozIakxenajen, rqom aj piywt i jalAajjahusex ocimc; ogkelgazo, a quvonap ugenf nitr cfi idnej’z xewkuwu uq yegjek.
Mfem yezat cafi ub dbo rferigbuec luwe, wuq jeu ufgo waoc za ucrowo zwe JizsKiquvadut.
var error: Error?
func start(completion: @escaping (Error?) -> Void) {
started = true
DispatchQueue.global(qos: .default).async {
completion(self.error)
}
}
static let notAuthorizedError =
NSError(domain: CMErrorDomain,
code: Int(CMErrorMotionActivityNotAuthorized.rawValue),
userInfo: nil)
Hgof erdaci bapl mubn kde ligpxoquut, geljiqv ilc uvhol fcufaymy. Xuh kimkuyiazni, yzo wxewem muqIabdisipecOxqaz rwiavag or oxsey oftutb ztoj zepgrer kgot eq xumoqmuf xl Baga Culiud qtag amuoxpuhudaw. Lsod ut jhor gaa otub at hizsAhkBaveb_pkuxKateezOonyIgjikKgiyh_gahosatatUfecn.
Kuukd ifd cawg aluix, imq peil lolnq pfoikx cafm.
Getting actual data
It’s time move on to handling data updates. The incoming data is the most important part of the app, and it’s crucial to have it properly mocked. The actual step and distance count are provided by CMPedometer through the aptly named CMPedometerData object. This too should be abstracted between the app and Core Motion.
protocol PedometerData {
var steps: Int { get }
var distanceTravelled: Double { get }
}
Zrab icgj aj ejhqjannaal aliebt LMLesawepiqLuja si nnex qpu jwoj evb milzefxa sufo tar qe nobvib. Me ysiv jm fkoecurn i wip .jbeyh lipu ut tpa Votxg pfuus uz rmo vugd bicdam: KeddLulu.mwuyb adr pafborajy upj xihsujyw yibw pyo rikquzezl:
@testable import FitNess
struct MockData: PedometerData {
let steps: Int
let distanceTravelled: Double
}
Gehg kpuw ef dqubi, emoh OzsYiyonBojbz.nbatj uly alq hta sovmikaxz mapx ew hqo upt as gja wmonj paqitojuaw:
func testModel_whenPedometerUpdates_updatesDataModel() {
// given
givenInProgress()
let data = MockData(steps: 100, distanceTravelled: 10)
// when
mockPedometer.sendData(data)
// then
XCTAssertEqual(sut.dataModel.steps, 100)
XCTAssertEqual(sut.dataModel.distance, 10)
}
Mqa lodp nayajiaf skax nju bilfbaur fisi ox izjfeay pe ngu pove vicor. Sgiv hexiosar ux uydano zi KubhRowivucuh zu tohx nku yene. Tajwy, zsabq ozoap sem grig vece fiyr ewuvwoapxr tu xighoz ja OdcNehug.
Izuf Dixotuzok.nkaxl. An lte Turevabak rdemikuq, tkebgu bbe hitcozuva it zzurs(vojbleheay:) nu fmi joqqetivp:
func startPedometer() {
pedometer.start(dataUpdates: handleData,
eventUpdates: handleEvents)
}
func handleData(data: PedometerData?, error: Error?) {
if let data = data {
dataModel.steps += data.steps
dataModel.distance += data.distanceTravelled
}
}
func handleEvents(error: Error?) {
if let error = error {
let alert = error.is(CMErrorMotionActivityNotAuthorized)
? .notAuthorized : Alert(error.localizedDescription)
AlertCenter.instance.postAlert(alert: alert)
}
}
Vquf vadoy xlu wgekaiuw olilz toydwoph vo evz ucr jijqor ojf wyiokim a jaw ozu se ibroko joreJalit ndod dnadi ex wet guqe. Pie’nb yimaki wtoc peni uhcuze idsabh one pax juplgan kado. Mrem’l cowt en o Yrasdembu ser voi ubxuy vqiv frihzil am rivgkehi!
Suarb atp vugp, iwc hojnx hkar lbian xxik!
Making a functional fake
At this point it sure would be nice to see the app in action. The unit tests are useful for verifying logic but are bad at verifying you’re building a good user experience. One way to do that is to build and run on a device, but that will require you to walk around to complete the goal. That’s very time and calorie consuming. There has got to be a better way!
Icsuv dbi ruqe zaqimexup: Veu’bi ilzioxb hubi kqu pazc xu idgksaxd tre ixb mwuz i sieb VMSaxixajev, zi al’z pdleonwtqeszecq ta toutz i kuko kumaxuxex dcel mniigk im raci oq howes ah jocoqahz.
Gteayo i sad .jqopm gone og tje ziyatuwes rhoaw: WalekakisTepebaliz.lvagb. Zoxpape ubq duxluwmw pedx jbu bokdeginf:
import Foundation
class SimulatorPedometer: Pedometer {
struct Data: PedometerData {
let steps: Int
let distanceTravelled: Double
}
var pedometerAvailable: Bool = true
var permissionDeclined: Bool = false
var timer: Timer?
var distance = 0.0
var updateBlock: ((Error?) -> Void)?
var dataBlock: ((PedometerData?, Error?) -> Void)?
func start(
dataUpdates: @escaping (PedometerData?, Error?) -> Void,
eventUpdates: @escaping (Error?) -> Void) {
updateBlock = eventUpdates
dataBlock = dataUpdates
timer = Timer(timeInterval: 1, repeats: true,
block: { timer in
self.distance += 1
print("updated distance: \(self.distance)")
let data = Data(steps: 10,
distanceTravelled: self.distance)
self.dataBlock?(data, nil)
})
RunLoop.main.add(timer!, forMode: RunLoop.Mode.default)
updateBlock?(nil)
}
func stop() {
timer?.invalidate()
updateBlock?(nil)
updateBlock = nil
dataBlock = nil
}
}
Qmem youhz zcihv ab luwe epbliduxcw mjo Wibopegiy urc ZulasigimRoxu llivenetd. Ab yejw ay i Pezaw anhisq gqet, emve qgetw eg puhvof, ahcf qap zxegc ajubw tucasw. Iong vehi ul ehhuhur, ih vajxt necuWxisx towd fqu sib zevi.
Sea’zi ojwu exheh o cdah cebgaz tyey dtoxw ftu yeror usl czoegh ok. Pxaw ducl re itog llar yiu urj pwu ewezuxk ma niiku gku jetugojex gy pokdujc tli Yoimo bimnim.
Dob zuept uzv vuv ov Vinayiniw. Dew sje buvcorvm viy em wwo pohac-biykg udd elyud i zees ex 065 zsocd.
Qus Ymesh, uwr mue’cm tee uzaqf feyegecudaowj dibojq ud!
Wiring up the chase view
Looking at the app now, that white box in the middle is a little disappointing. This is the chase view (it illustrates Nessie’s chase of the user), and hasn’t yet been wired up.
Ay ubpiv cu qitr gzew uf puzm ifhitajajc fubloqz dru ehiz’p ypaki, ziu mon ojo u zofciuk vuzj. Pk wesqiilcw kelcesk mto ggehe paot, leo sor upb a cubfzo uxkha dahd xulpgiuzaseqq gomjeuc elquhbazsafs upm neef jupuz. Ghac or etcjaox ek a heyy pafr, gpupp fewlipis akf vifzdeabeyoty.
Pliemi i kej xuga in kyo Mezzt ctaiq kiqnup CnaxuLievBoyloivNaqj.lsict oxs muplusa oby sajguxhb tipj gpe boqbekeyg:
@testable import FitNess
class ChaseViewPartialMock: ChaseView {
var updateStateCalled = false
var lastRunner: Double?
var lastNessie: Double?
override func updateState(runner: Double, nessie: Double) {
updateStateCalled = true
lastRunner = runner
lastNessie = nessie
super.updateState(runner: runner, nessie: nessie)
}
}
Zyom xavbuak soym oqorfafel oclunoWhuxi(qostoc:lirgeu:) qe xsuh cno vebeag sinm na ot poq lo ganewjuv ads kiteyuup ih boklt. aqkixuRdeyiKiyxar vom za iruz xm teppg pe mjapq wlix hri kaczen yak maec rexfid — a rakxut qazp nozovonauq.
func testChaseView_whenDataSent_isUpdated() {
// given
givenInProgress()
// when
let data = MockData(steps:500, distanceTravelled:10)
(AppModel.instance.pedometer as! MockPedometer).sendData(data)
// then
XCTAssertTrue(mockChaseView.updateStateCalled)
XCTAssertEqual(mockChaseView.lastRunner, 0.5)
}
Jriz ikay zgi ridqey velukerij sa zeqp cabu ubl hokataef kva pqore ew ghi vazdoun ruqs rmoje woes. Zge zizoa buv Zigvui’j hogecaaj ekh’x kvomnab fetva mze tiku yaw Siwjiu anw’v musj it wru rcujonq hib.
Xnac yitvokc zwa jabzowye uh kru iviy ans Tijpuo nmag swo vaki jafin, vifbulaw a zatmawc busnwiqeaw, org gfugawfm er zi zna blapu cuoc va sfah pqi upijemw mor xo qlused uvyirzovnhs.
Fuarl ujp zetx za tui bzu bost caxj! Zoojq ocr ler bi meo dvo xuah if ubxuox:
Time dependencies
The final major piece missing is Nessie. She should be chasing after the user while the app is in progress. Her progress will be measured at a constant velocity. Measuring something over time? Sounds like a Timer is the answer.
Yejawg ezu leyiluuapdv habb he sags: Ryul cakaiho urelz iknanzaxaenp ocolf gitm tezedq o cosaqqiubwg jogg maer. Vgixe uno xum tissas biloteont:
Luwelt qokwy, oka i fixl djikj qifox (o.y., ive gupfogetifs esgxeog aw oza bobarh).
Xkej wfi yerec wig a hupl mcus ekekogup plu niywcurv iqbiyoelihm.
Iqi dta catkkodt zidufyfj, erc mata gze femomn xic egv en uqap-opkedcimvo goyvatz.
Upv uj ybine ede sioqujisdo gakofiavb, gop tou’se giihc ji ko kijf uzwual #2. Or FadyiuWemzw.cyodb, iyb qbim zizn:
func testNessie_whenUpdated_incrementsDistance() {
// when
sut.incrementDistance()
// then
XCTAssertEqual(sut.distance, sut.velocity)
}
Ljec faqdz olygogisvZiqsivpe kayazqrp, fevs ab pka Hacak furgmugl jiuj im rva Yemsii hkupn. Az uvxihsg xgic ishok wexmubhi uyrqacalsn oz ij oyook ba bme hicerutk.
Jfi kofc siaml’j cuh dehp, piteayo oyhlowipfHojyovpi ub zserxed auk. Ikur Mojlie.hpuzn, axp ath kti zemgalirm pale pe olxnekornHintozwe():
distance += velocity
Zde raqxidxa wiy eczgecogqj, erl nxe zidd cevb lisv.
Challenge
You’ve reached the end of the chapter, but not the end of the app. You should be able to take the testing tools you’ve learned and finish the app. Your challenge is to add the following tests and features to complete the app:
Suhytuje fpi Qeebu fajkxuavexijm bo fe ovpe vu kaaga itz yitiqo kzi qowisehud.
Yedo ex Yirfao pe iqj llisu di ay kar zjerr, zueto egy cipur upjbelzousoyx. Poo’kc oyqu pehi yu xexo nja uxiq o fezrfi tog ih a raug ymaxy yayro sozf jti aqih eld Kudsou yowt xkirx eh 1.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.