如何为仅含类方法的FileManager实现依赖注入以测试AttachmentsRepository
看起来你想给自定义的FileManager类(全是类方法那种)做依赖注入,方便测试AttachmentsRepository对吧?你提到的“ex...”应该是指**提取协议(Extract Protocol)**的方案,这正是OC里处理这类场景的标准做法,我给你一步步拆解具体实现:
步骤1:抽象协议,解耦依赖
首先我们需要把FileManager里用到的类方法抽象成一个协议,这样AttachmentsRepository只需要依赖这个协议,而不是具体的FileManager类:
// FileManagerProtocol.h #import <Foundation/Foundation.h> @protocol FileManagerProtocol <NSObject> // 把你需要用到的FileManager类方法转成协议的实例方法 - (void)writeData:(NSData *)data toFile:(NSString *)filePath; // 如果还有其他需要测试的类方法,都加到这里 @end
步骤2:让原FileManager遵循协议
因为你的FileManager只有类方法,我们可以让它遵循上面的协议,用实例方法来包装自己的类方法:
// FileManager.m #import "FileManager.h" #import "FileManagerProtocol.h" @implementation FileManager // 原有的类方法实现 + (void)writeData:(NSData *)data toFile:(NSString *)filePath { // 你的原有逻辑 } // 实现协议的实例方法,内部调用类方法 - (void)writeData:(NSData *)data toFile:(NSString *)filePath { [FileManager writeData:data toFile:filePath]; } // 其他协议方法同理包装 @end
步骤3:修改AttachmentsRepository,支持注入
接下来修改AttachmentsRepository,添加一个遵循FileManagerProtocol的属性,并且提供带参数的初始化方法,同时保留默认初始化(用原FileManager实例),不影响原有业务代码:
// AttachmentsRepository.h #import <Foundation/Foundation.h> #import "FileManagerProtocol.h" @interface AttachmentsRepository : NSObject @property (nonatomic, strong) id<FileManagerProtocol> fileManager; // 带注入参数的初始化方法 - (instancetype)initWithFileManager:(id<FileManagerProtocol>)fileManager; // 原有业务方法 - (void)newAttachment:(NSData *)attachmentData; @end
// AttachmentsRepository.m #import "AttachmentsRepository.h" @implementation AttachmentsRepository - (instancetype)initWithFileManager:(id<FileManagerProtocol>)fileManager { self = [super init]; if (self) { _fileManager = fileManager; } return self; } // 默认初始化,使用原FileManager实例 - (instancetype)init { return [self initWithFileManager:[[FileManager alloc] init]]; } - (void)newAttachment:(NSData *)attachmentData { NSString *generatedPath = @"/some/generated/path"; // 现在调用协议方法,而不是直接调用FileManager的类方法 [self.fileManager writeData:attachmentData toFile:generatedPath]; } @end
步骤4:创建Mock类用于测试
现在我们可以写一个Mock类遵循FileManagerProtocol,用来验证AttachmentsRepository是否正确调用了文件写入方法:
// MockFileManager.h #import <Foundation/Foundation.h> #import "FileManagerProtocol.h" @interface MockFileManager : NSObject <FileManagerProtocol> // 用来验证方法是否被调用 @property (nonatomic, assign) BOOL didCallWriteData; // 用来存储传递的参数,方便验证 @property (nonatomic, strong) NSData *passedData; @property (nonatomic, strong) NSString *passedFilePath; @end
// MockFileManager.m #import "MockFileManager.h" @implementation MockFileManager - (void)writeData:(NSData *)data toFile:(NSString *)filePath { self.didCallWriteData = YES; self.passedData = data; self.passedFilePath = filePath; } @end
步骤5:在测试用例中注入Mock
最后在测试代码里,把MockFileManager注入到AttachmentsRepository,就可以轻松验证交互逻辑了:
// AttachmentsRepositoryTests.m #import <XCTest/XCTest.h> #import "AttachmentsRepository.h" #import "MockFileManager.h" @interface AttachmentsRepositoryTests : XCTestCase @property (nonatomic, strong) AttachmentsRepository *repo; @property (nonatomic, strong) MockFileManager *mockFileManager; @end @implementation AttachmentsRepositoryTests - (void)setUp { [super setUp]; // 初始化Mock和Repository,注入Mock self.mockFileManager = [[MockFileManager alloc] init]; self.repo = [[AttachmentsRepository alloc] initWithFileManager:self.mockFileManager]; } - (void)testNewAttachmentWritesDataCorrectly { // 准备测试数据 NSData *testData = [@"test_attachment" dataUsingEncoding:NSUTF8StringEncoding]; // 调用业务方法 [self.repo newAttachment:testData]; // 验证Mock的状态,确认方法被正确调用 XCTAssertTrue(self.mockFileManager.didCallWriteData, @"文件写入方法应该被调用"); XCTAssertEqualObjects(self.mockFileManager.passedData, testData, @"传递的数据不匹配"); XCTAssertEqualObjects(self.mockFileManager.passedFilePath, @"/some/generated/path", @"文件路径不匹配"); } @end
为什么这种方案有效?
原来的代码直接依赖FileManager的类方法,耦合度极高,无法在测试时替换实现。通过抽象协议,我们把AttachmentsRepository的依赖从具体类转为抽象协议,这样在测试时可以轻松注入Mock对象,验证它是否正确调用了文件操作方法,而不需要真正操作文件系统,让测试更快、更可靠。
内容的提问来源于stack exchange,提问作者h3dkandi

