Mocking and Dependency Injection: Fakes, Stubs, Spies, Mocks, and Testable Design
Learn the precise, distinct meanings of the four test-double categories — fakes, stubs, spies, and mocks — and the dependency injection techniques (initializer, property, and factory injection) that, combined with protocol-typed dependencies, make substituting any of them into a class under test possible in the first place.
The previous lesson ended by previewing test doubles as a category and leaving the real depth for here, because the four terms — fake, stub, spy, and mock — are routinely used interchangeably in casual conversation despite meaning genuinely different things, and that sloppiness causes real confusion: a developer who says 'I'll just mock it' when they mean 'I'll return a canned value' is glossing over a distinction that matters once a team is trying to read and trust each other's tests. This lesson first makes the four terms precise, with a worked example of each. It then turns to dependency injection, the architectural technique that makes swapping any of those doubles into a class under test possible at all — because none of this works unless a class receives its collaborators from the outside rather than building them internally, and unless it depends on a protocol type rather than one specific concrete class. The spec's own example — a view controller depending on a service protocol, with a real service and a fake service both able to sit behind it — is the running example this entire lesson is organized around.
Why protocols are the foundation this whole lesson depends on
The earlier Protocols lessons established that a protocol declares a set of methods without providing any implementation, and that any class, of any inheritance lineage, can adopt a protocol by implementing those methods and declaring conformance. A variable, property, or parameter typed as id<SomeProtocol> — or, more idiomatically, as a pointer to a class typed with the protocol in angle brackets — can hold an instance of any class that conforms, without the code holding that reference knowing or caring which concrete class it actually got.
That single fact is what makes everything in this lesson possible. If a view controller holds a property typed as a concrete class — NetworkService *service — then, by definition, the only object that can ever sit in that property is a NetworkService or a subclass of it. There is no way to swap in a lightweight substitute during a test without either genuinely subclassing NetworkService (dragging along whatever real behavior it doesn't override) or resorting to fragile tricks like method swizzling. But if that same property is typed as id<ServiceProtocol> — a protocol type — then any object that conforms to ServiceProtocol can sit there, including one written purely for testing that has nothing to do with NetworkService's inheritance hierarchy at all.
This is the precise mechanism behind the diagram this lesson is built around: a view controller depends on a service protocol, not on a concrete service class, and that one design decision is what allows a real service and a fake, stub, spy, or mock service to be genuinely interchangeable from the view controller's point of view. Depending on a protocol rather than a concrete class isn't a stylistic preference — it's the specific architectural fact that makes test doubles possible at all.
Fake objects: a working but simplified alternative implementation
A fake is a test double that has real, working logic behind it — it genuinely does the thing it's asked to do — but implements it in a simplified way that would be unsuitable for production, usually specifically to avoid a slow or external dependency. The canonical example is an in-memory fake data store standing in for a real, network-backed or disk-backed one: a FakeUserStore might keep users in an NSMutableDictionary in memory, genuinely supporting save, fetch, and delete with real, correct logic, while a real UserStore does the equivalent work by hitting a network API or a Core Data stack.
What makes something a fake rather than a stub is exactly this: a fake has real behavior. Ask a FakeUserStore to save a user and then fetch it back, and it will actually store the value and actually return it — the round trip genuinely works, because the fake's internal logic is real, just simplified (no persistence across app launches, no network latency, no serialization). A stub, by contrast, as the next section covers, has no real logic behind it at all — it just returns whatever canned value it was told to return, regardless of what was actually asked of it beforehand.
Fakes are particularly valuable for testing code that needs to exercise a genuine sequence of operations against a dependency — save this, then fetch it, then delete it, then confirm it's gone — where a stub's static canned responses wouldn't capture the real, stateful behavior being tested. The tradeoff is that a fake takes more effort to write correctly than a stub does, since it has to actually work, not just return a fixed value.
Stubs: canned responses, no real logic
A stub is a test double that returns pre-programmed, canned responses to specific calls, with no real logic driving those responses at all — ask it for a user with ID '42' and it hands back whatever specific User object the test told it to hand back, full stop, regardless of whether ID '42' was ever actually 'stored' anywhere or whether the stub even has a notion of storage in the first place.
Stubs exist to make a test's inputs completely predictable and controllable. Testing a view controller's error-handling code, for instance, needs a service that reliably returns a specific error every time it's asked, on demand, without needing to somehow force a real service or even a real fake to genuinely fail in exactly that way. A StubServiceProtocol conformer configured to always call its completion handler with a specific NSError gives the test exactly that: guaranteed, repeatable, deterministic behavior with zero real logic behind it.
The core distinction to keep straight against a fake: a stub's responses are configured directly by the test, per call, ahead of time, and never actually reflect any genuine underlying logic — a stub asked to fetch a user it was never told about does not fail the way a real (or fake) store would; it simply returns whatever it was told to return, or nothing, because it isn't actually storing or computing anything at all.
Spies: recording how they were called
A spy is a test double that, in addition to whatever response behavior it provides (often stub-like canned responses), also records how it was called — how many times each method was invoked, and with what arguments — so that a test can later assert on the interaction itself, not just on some return value. A SpyAnalyticsService might record every event name and every parameters dictionary passed to -logEvent:parameters:, into an array, purely so a test can afterward assert that logEvent:parameters: was called exactly once, with exactly the event name and parameters the code under test was supposed to send.
This is the distinguishing feature of a spy: the assertion the test cares about is not 'what value did I get back' (that's what a stub is for) but 'was this method called, and was it called correctly' — a form of behavior verification rather than state verification. Testing that a purchase-confirmation view controller correctly notifies an analytics service exactly once when a purchase completes is a spy's natural job: the view controller's own visible state might look identical whether or not it made that call, so only a spy, watching the call itself, can verify it actually happened.
A spy commonly wraps or is built on top of a stub — it needs to return something plausible to keep the code under test running normally — but adds the recording on top. The key property to hold onto is that a spy is checked *after* the fact ('let me look at what got recorded and assert on it'), which is what separates it from a mock, covered next, which is checked instead by pre-declared expectations.
Mocks: pre-programmed expectations that can fail the test themselves
A mock is a test double pre-programmed, before the code under test even runs, with expectations about exactly how it should be called — which methods, how many times, with what arguments — and that can itself cause the test to fail if those expectations are not met by the time the test finishes. Where a spy passively records and leaves the assertion for the test to make explicitly afterward, a mock actively declares up front 'I expect fetchUser:completion: to be called exactly once, with ID 42' and fails on its own if that expectation isn't satisfied.
This makes mocks the most behavior-verification-heavy of the four categories: the specification of correct behavior is baked into the mock's setup itself, before the code under test ever runs, rather than checked against recorded data afterward. A MockPaymentService configured with an expectation that -chargeCard:amount:completion: is called exactly once can fail the test immediately if that method is never called at all, or is called twice, or is called with the wrong amount — without the test needing a separate, explicit assertion afterward to catch any of those cases, because the mock is enforcing its own expectations as part of its behavior.
The practical distinction worth holding onto between a spy and a mock: a spy records passively and leaves verification to explicit assertions the test writes afterward ('assert that the recorded call count equals one'), while a mock is configured with expectations up front and can fail the test on its own, as part of just running, if the code under test doesn't interact with it the way it was told to expect. Both are behavior-verification doubles — concerned with *how* a collaborator was used — as opposed to fakes and stubs, which are primarily concerned with *what value* a collaborator hands back.
The four terms side by side
It's worth stating the four definitions together, precisely, since the whole point of this lesson is to stop treating them as synonyms. A fake has real, working, simplified logic behind it (an in-memory store that genuinely stores and retrieves). A stub has no real logic at all — it returns whatever canned response it was configured with, regardless of what was actually asked. A spy behaves like a stub but additionally records how it was called, leaving the test to assert on that recorded call history afterward. A mock is pre-loaded with expectations about how it should be called and can fail the test on its own if those expectations go unmet, without needing a separate assertion afterward.
A rough way to organize the four: fakes and stubs are primarily about controlling what a dependency *returns*, so the code under test can be driven down a specific path (a success path, an error path, an empty-result path) deterministically. Spies and mocks are primarily about verifying *how* the code under test used its dependency — which methods it called, how often, and with what — rather than about controlling what came back. Choosing the right one for a given test is a matter of asking which question the test actually needs answered: 'did my code produce the right result given this input' points toward a fake or a stub; 'did my code correctly interact with its collaborator' points toward a spy or a mock.
None of the four is universally 'better' than the others — a test suite that needs to verify a view controller correctly displays a fetched list of items reaches for a stub or fake ServiceProtocol conformer; a test suite that needs to verify a checkout flow correctly calls an analytics or payment collaborator exactly once reaches for a spy or a mock instead. Real test suites use all four, chosen deliberately per test based on which question that specific test is actually trying to answer.
Dependency injection: receiving collaborators instead of constructing them
None of the four test-double categories above are usable in practice unless the class under test can be handed a test double instead of its real collaborator in the first place — and that is exactly what dependency injection provides. The core idea is simple to state: a class receives its dependencies from the outside, passed in by whoever creates or configures it, rather than constructing those dependencies itself internally as a hidden implementation detail.
Consider the diagram this lesson is built around: a ViewController depends on a Service Protocol, and either a Real Service or a Fake Service can sit behind that protocol at runtime. If ViewController's own -init method internally wrote NetworkService *service = [[NetworkService alloc] init]; and stored the result, no outside code — including a test — would ever have a way to get a Fake Service in there instead; the dependency would be permanently, invisibly hardwired to the one concrete class. Dependency injection is the discipline of never letting that hardwiring happen: ViewController instead receives *some* object conforming to ServiceProtocol from outside, at the point it's created or configured, with no opinion at all about which concrete class that object actually is.
This is a small change in where a single line of object-creation code lives, but it has an outsized effect on testability specifically because it's what turns 'this class depends on a protocol' (the previous section's point) into something a test can actually act on. A protocol-typed dependency that's still constructed internally is still just as untestable as a concrete-typed one; it's the combination of a protocol type and dependency injection together that lets a test hand the class under test a fake, stub, spy, or mock instead of the real thing.
Initializer, property, and factory injection
Initializer injection passes a dependency in through an init method, as a required parameter, at the moment the object is constructed — for instance, - (instancetype)initWithService:(id<ServiceProtocol>)service. This is generally the preferred form wherever it's practical, for a specific reason: it makes it structurally impossible for a fully-constructed instance of the class to exist without its required dependency already in place, since there is no init path that skips providing it. A test, or production code, calling [[ViewController alloc] initWithService:fakeService] gets back an object that is guaranteed, by construction, to already have a working service — there is no intermediate state where the object exists but its dependency doesn't.
Property injection instead exposes the dependency as a settable property, assigned after the object has already been constructed — for instance, an ordinary @property (nonatomic, strong) id<ServiceProtocol> service; that a test or caller sets directly after calling a plain init. This is more flexible than initializer injection for dependencies that are genuinely optional, or for situations where requiring every dependency through the initializer would make construction unwieldy (many optional collaborators, for instance) — but it comes with a real cost: it allows an object to exist, briefly or not so briefly, in an incomplete state, with the dependency still nil, before the caller gets around to setting it. Code that runs between construction and that property assignment can encounter a nil service it wasn't expecting.
Factory injection injects not the dependency itself but a factory — an object or, in Objective-C, often a block — that knows how to create the dependency on demand, whenever it's actually needed. This is useful specifically when a single dependency needs to be created more than once (a new network request object per call, rather than one shared instance) or when creating it correctly needs a runtime parameter that isn't known yet at the point the injection happens — for instance, a ConnectionFactory block that takes a URL decided only later, at call time, and returns a freshly configured connection object for that specific URL. A test can inject a factory block that returns a stub or fake instead of a real connection, giving the same substitutability as the other two forms, just at the point of each individual creation rather than once up front.
None of the three is exclusively correct; real codebases mix them based on the shape of the dependency in question. A required, always-needed collaborator like the Service Protocol in this lesson's running example is the textbook case for initializer injection. An optional collaborator, like an analytics logger that's fine to leave unset in some configurations, is a reasonable case for property injection. A collaborator that needs to be freshly created per use, with parameters only known at call time, calls for factory injection.
Tying it together: injection plus protocols is what makes isolation possible
Step back and the whole picture this lesson has been building connects directly to the previous lesson's opening idea of unit-test isolation: a unit test wants to verify one class's logic without dragging along the network, a database, or any other slow or side-effect-having real collaborator. Dependency injection is the mechanism that lets a test hand the class under test something other than that real collaborator; a protocol-typed dependency is what makes the substitute type-compatible in the first place, since the real service and any test double both simply need to conform to the same ServiceProtocol.
Put those two together and the ViewController/Service Protocol diagram stops being an abstract picture and becomes a direct testing recipe: production code constructs a ViewController with a Real Service injected in (through whichever injection form fits), while a test constructs the exact same ViewController class, through the exact same injection point, with a Fake, Stub, Spy, or Mock Service injected in instead — whichever of the four the specific test actually needs, per the earlier taxonomy. The ViewController's own code never changes between the two cases and, in a well-designed version, has no idea which one it's actually talking to.
This is why dependency injection and protocol-typed dependencies are worth treating as foundational, architectural habits rather than testing-specific tricks bolted on afterward: a class built this way from the start is simply more testable, in isolation, with the full range of fakes, stubs, spies, and mocks available to whichever test needs them — without any of that requiring the class's own production code to be restructured, weakened, or compromised in order to make testing possible after the fact.
Code Examples
ViewController never mentions a concrete service class anywhere in its own implementation — its stored ivar and its initializer parameter are both typed as id<ServiceProtocol>. Initializer injection guarantees that any ViewController that exists at all was handed a working service at construction time, and that service can be a real, network-backed implementation in production or any test double conforming to the same protocol in a test.
• (instancetype)initWithService:(id<ServiceProtocol>)service;
Common mistake: Writing initWithService: but then also having a plain init that internally creates a real NetworkService reintroduces the exact hardwiring dependency injection is meant to eliminate.
FakeUserStore genuinely stores whatever it's given and hands back exactly that on a matching fetch — a real, working round trip, just an in-memory simplification of a real persistence layer. StubUserStore has no real storage logic at all: saveUser: does nothing, and fetchUserWithID: always returns whatever userToReturn was preset to, completely ignoring the userID argument. A test that needs to verify a genuine save-then-fetch sequence needs the fake; a test that just needs fetchUserWithID: to deterministically return one specific User, regardless of what ID is asked for, reaches for the stub instead. Notice the stub's fetchUserWithID: never even looks at its userID parameter — that's the tell that it has no real logic, only a fixed canned answer.
• (User *)fetchUserWithID:(NSString *)userID { return self.userToReturn; }
SpyAnalyticsService records every call it receives (count and last event name) but does nothing to assert correctness itself — the test reads those recorded values afterward and asserts on them explicitly. The mock example (using OCMock-style syntax, representative of common Objective-C mocking libraries) works the opposite way: the expectation that chargeCard:amount:completion: will be called with specific arguments is declared before completePurchaseWithPaymentService: even runs, and calling verify checks that expectation, failing the test right there if it wasn't satisfied — no separate, explicit assertion needed.
[mockPayment verify]; // fails the test right here if chargeCard:amount:completion: wasn't called as expected
Common mistake: Calling a spy's recorded properties 'mocking' blurs a real distinction: a spy needs an explicit assertion afterward to fail a test; a genuine mock enforces its expectations on its own.
ReportGenerator's logger is injected via a settable property, appropriate because logging is genuinely optional — generateReport works fine whether or not a logger was ever set, and a test can leave it nil entirely to skip logging concerns. DownloadManager instead receives a factory block, appropriate because a fresh ConnectionProtocol object is needed per URL, and the URL itself isn't known until downloadFromURL: is actually called — a single injected connection instance, provided once via initializer injection, wouldn't work here at all. A test can inject a factory block that ignores the URL entirely and always returns a stub connection. Contrast both against initializer injection's ViewController example above: three genuinely different injection mechanisms, each fitting a different shape of dependency.
typedef id<ConnectionProtocol> (^ConnectionFactory)(NSURL *url);
@protocol ServiceProtocol <NSObject>
- (void)fetchUserWithID:(NSString *)userID
completion:(void (^)(User *user, NSError *error))completion;
@end
@interface ViewController : UIViewController
- (instancetype)initWithService:(id<ServiceProtocol>)service;
@end
@implementation ViewController {
id<ServiceProtocol> _service;
}
- (instancetype)initWithService:(id<ServiceProtocol>)service {
self = [super initWithNibName:nil bundle:nil];
if (self) {
_service = service;
}
return self;
}
- (void)loadUser {
[_service fetchUserWithID:@"42" completion:^(User *user, NSError *error) {
// update UI with user or show error
}];
}
@end// A FAKE: real, working, simplified logic
@interface FakeUserStore : NSObject <UserStoreProtocol>
@property (nonatomic, strong) NSMutableDictionary<NSString *, User *> *storage;
@end
@implementation FakeUserStore
- (instancetype)init {
self = [super init];
if (self) { _storage = [NSMutableDictionary dictionary]; }
return self;
}
- (void)saveUser:(User *)user { self.storage[user.userID] = user; }
- (User *)fetchUserWithID:(NSString *)userID { return self.storage[userID]; }
@end
// A STUB: no real logic, just a canned response
@interface StubUserStore : NSObject <UserStoreProtocol>
@property (nonatomic, strong) User *userToReturn;
@end
@implementation StubUserStore
- (void)saveUser:(User *)user { /* intentionally does nothing */ }
- (User *)fetchUserWithID:(NSString *)userID { return self.userToReturn; }
@end// A SPY: records how it was called, for the test to assert on afterward
@interface SpyAnalyticsService : NSObject <AnalyticsServiceProtocol>
@property (nonatomic, assign) NSInteger logEventCallCount;
@property (nonatomic, strong) NSString *lastLoggedEventName;
@end
@implementation SpyAnalyticsService
- (void)logEvent:(NSString *)eventName parameters:(NSDictionary *)parameters {
self.logEventCallCount += 1;
self.lastLoggedEventName = eventName;
}
@end
// test using the spy
- (void)testPurchaseLogsAnalyticsEvent {
SpyAnalyticsService *spy = [[SpyAnalyticsService alloc] init];
CheckoutController *controller = [[CheckoutController alloc] initWithAnalytics:spy];
[controller completePurchase];
XCTAssertEqual(spy.logEventCallCount, 1, @"Should log exactly one analytics event");
XCTAssertEqualObjects(spy.lastLoggedEventName, @"purchase_completed");
}
// A MOCK (conceptual): expectations declared up front, checked by the mock itself
id<PaymentServiceProtocol> mockPayment = [OCMockObject mockForProtocol:@protocol(PaymentServiceProtocol)];
[[mockPayment expect] chargeCard:@"tok_123" amount:19.99 completion:[OCMArg any]];
[controller completePurchaseWithPayme
…// PROPERTY INJECTION: optional dependency, settable after construction
@interface ReportGenerator : NSObject
@property (nonatomic, strong, nullable) id<LoggerProtocol> logger; // optional
- (void)generateReport;
@end
@implementation ReportGenerator
- (void)generateReport {
[self.logger logMessage:@"Report generation started"]; // logger may be nil; fine to skip
// ... build the report ...
}
@end
// FACTORY INJECTION: a block that creates a fresh dependency per use, with a runtime parameter
typedef id<ConnectionProtocol> (^ConnectionFactory)(NSURL *url);
@interface DownloadManager : NSObject
- (instancetype)initWithConnectionFactory:(ConnectionFactory)factory;
- (void)downloadFromURL:(NSURL *)url;
@end
@implementation DownloadManager {
ConnectionFactory _connectionFactory;
}
- (instancetype)initWithConnectionFactory:(ConnectionFactory)factory {
self = [super init];
if (self) { _connectionFactory = [factory copy]; }
return self;
}
- (void)downloadFromURL:(NSURL *)url {
id<ConnectionProtocol> connection = _connectionFactory(url); // fresh, per-URL connection
[connection open];
}
@endA view controller depending on a service protocol, not a concrete class
ViewController depends only on Service Protocol, never on a concrete class. Because both Real Service and Fake Service (or a Stub, Spy, or Mock Service) conform to that same protocol, either one can be injected in behind it — in production, the real one; in a test, a double chosen for that test's purpose.
ViewController
↓
Service Protocol
↓
┌─────────────┐
│ Real Service │
│ Fake Service │
└─────────────┘Why mocking frameworks exist
Writing a hand-rolled fake, stub, spy, or mock class for every protocol in a codebase, as this lesson's examples do, works fine and is genuinely worth understanding from first principles — but it does not scale gracefully to a codebase with dozens of protocols and hundreds of tests, each needing its own hand-written double. Objective-C mocking frameworks such as OCMock exist specifically to remove that repetition: they can generate a stub, spy, or mock for a protocol or class at runtime, with expectations and canned return values configured through a compact API, without a developer having to write and maintain a dedicated @interface/@implementation pair by hand for every single test double a suite needs. Understanding the underlying concepts this lesson covers — precisely what a fake, stub, spy, and mock actually are and when each is the right tool — is what makes reading and writing correct tests with a framework like that straightforward, rather than a decorative wrapper around code whose actual testing strategy no one on the team could clearly explain.
Calling every test double a 'mock,' regardless of whether it actually enforces expectations, records calls, or just returns a fixed value.
Use the precise term: fake (real simplified logic), stub (canned response, no logic), spy (records calls for later assertion), or mock (pre-declared expectations, can fail the test itself).
Blurring the four terms together makes it harder to read and trust a team's tests, since 'I mocked it' no longer tells a reader which of four genuinely different testing strategies was actually used.
Depending on a concrete class (NetworkService *service) inside a class that needs to be unit tested.
Depend on a protocol type (id<ServiceProtocol> service) instead, so any conforming object, real or a test double, can be substituted in.
A dependency typed to one concrete class structurally cannot accept a lightweight test double, no matter how the object is injected, since the double would need to inherit from that exact class.
Constructing a dependency internally inside an object's own init method, even though the dependency is typed as a protocol.
Receive the dependency from outside via initializer injection, property injection, or factory injection, and let the caller decide which concrete implementation to provide.
A protocol-typed dependency that is still constructed internally offers no way for a test to substitute anything else in — protocol typing and dependency injection are both required together, not either one alone.
Using property injection for a dependency the class cannot function correctly without.
Use initializer injection for required dependencies, so no instance of the class can exist without them already in place.
Property injection allows a temporarily incomplete object to exist with the dependency still nil; for a required dependency, code running before that property gets set can hit an unexpected nil.
What specifically makes a class's dependency on a service substitutable with a test double in the first place? A. Using NSObject as the dependency's declared type B. Depending on a protocol type rather than one concrete class C. Marking the dependency property as nonatomic D. Writing the dependency's methods as class methods instead of instance methods
B. Depending on a protocol type rather than one concrete class — A dependency typed to a protocol accepts any conforming object, real or a test double; a dependency typed to one concrete class structurally cannot.
What is the key difference between a fake and a stub? A. A fake is faster than a stub B. A fake has real, working (if simplified) logic, while a stub has no real logic and just returns a preset canned value C. A fake can only be used in unit tests while a stub can only be used in integration tests D. There is no meaningful difference
B. A fake has real, working (if simplified) logic, while a stub has no real logic and just returns a preset canned value — A fake genuinely performs the operation, simplified (like an in-memory store); a stub has no real behavior and returns whatever fixed response it was configured with, regardless of input.
A test double records how many times a method was called and with what arguments, and the test later makes explicit assertions on that recorded history. What kind of double is this? A. A stub B. A fake C. A spy D. A mock
C. A spy — A spy records call history for the test to assert on afterward; this passive recording plus later explicit assertion is exactly what distinguishes a spy from a mock, which enforces its own expectations.
What distinguishes a mock from a spy? A. A mock is pre-configured with expectations about how it should be called and can fail the test itself if unmet, while a spy just records calls for the test to check afterward B. A mock can only be used for networking code C. A spy is always faster to execute than a mock D. A mock never returns any value while a spy always does
A. A mock is pre-configured with expectations about how it should be called and can fail the test itself if unmet, while a spy just records calls for the test to check afterward — A mock declares expectations up front and enforces them itself (often via a verify call), which can fail the test directly; a spy leaves the verification to an explicit assertion the test writes afterward.
Why is initializer injection generally preferred over property injection for a required dependency? A. It uses less memory at runtime B. It guarantees a fully-constructed object can never exist without its required dependency already in place C. It is the only form Objective-C's compiler actually supports D. It avoids needing a protocol at all
B. It guarantees a fully-constructed object can never exist without its required dependency already in place — Since the dependency is a required initializer parameter, there is no way to end up with an instance of the class that exists yet lacks that dependency; property injection, by contrast, allows a temporarily incomplete object.
When is factory injection specifically the right choice over initializer or property injection? A. When the dependency never changes for the lifetime of the object B. When a dependency needs to be created multiple times, or needs a runtime parameter not known at the point the injection happens C. When the dependency is a primitive scalar like an NSInteger D. When the class has no dependencies at all
B. When a dependency needs to be created multiple times, or needs a runtime parameter not known at the point the injection happens — Factory injection provides an object or block that creates the dependency on demand, which suits cases needing repeated fresh instances or parameters (like a URL) only known at call time, rather than at construction time.
Design a WeatherViewController that depends on a WeatherServiceProtocol (with a method - (void)fetchWeatherForCity:(NSString *)city completion:(void (^)(Weather *weather, NSError *error))completion), injected via initializer injection. Then sketch, in words or code, one test using a stub WeatherServiceProtocol conformer that always returns a specific Weather object, and one test using a spy that records whether fetchWeatherForCity:completion: was called with the correct city name.
@protocol WeatherServiceProtocol <NSObject> - (void)fetchWeatherForCity:(NSString *)city completion:(void (^)(Weather *weather, NSError *error))completion; @end @interface WeatherViewController : UIViewController - (instancetype)initWithWeatherService:(id<WeatherServiceProtocol>)service; - (void)loadWeatherForCity:(NSString *)city; @end // Stub test: control the return value StubWeatherService *stub = [[StubWeatherService alloc] init]; stub.weatherToReturn = [Weather weatherWithTemperature:72 condition:@"Sunny"]; WeatherViewController *vc = [[WeatherViewController alloc] initWithWeatherService:stub]; [vc loadWeatherForCity:@"Austin"]; XCTAssertEqualObjects(vc.temperatureLabel.text, @"72\u00b0"); // Spy test: verify the call itself SpyWeatherService *spy = [[SpyWeatherService alloc] init]; WeatherViewController *vc2 = [[WeatherViewController alloc] initWithWeatherService:spy]; [vc2 loadWeatherForCity:@"Austin"]; XCTAssertEqual(spy.fetchCallCount, 1); XCTAssertEqualObjects(spy.lastRequestedCity, @"Austin");
The stub test is about controlling the returned value to check how the view controller displays it; the spy test is about verifying the view controller called the service correctly in the first place, regardless of what it returns.