如何调整我的视图模型以实现依赖注入 swiftui(用于以后的单元测试)

Posted

技术标签:

【中文标题】如何调整我的视图模型以实现依赖注入 swiftui(用于以后的单元测试)【英文标题】:How to adapt my viewmodel to implement dependancy injection swiftui (to later unit test) 【发布时间】:2021-04-25 09:13:48 【问题描述】:

目前我有多个 ViewModel,在阅读了一些其他帖子并获得了帮助之后,我被建议在我的班级中实现依赖注入,以便对我的逻辑进行单元测试。

但是我不确定如何使我的以下类适应依赖注入,我不确定是否也需要更改我的 DataManager 类。

这是我的 ViewModel 的一个示例:

 class CalorieProgressViewModel: ObservableObject 


@Published var calorieProgress = [CalorieProgressEntity]()


init() 



//Creating the data
func addCalorieProgressData(id: UUID, calorieProgress: Double, fatProgress: Double, carbProgress: Double, proteinPogress: Double, created: Date) 
    CoreDataManager.shared.addCalorieProgressData(id: id, calorieProgress: calorieProgress, fatProgress: fatProgress, proteinProgress: proteinPogress, carbProgress: carbProgress, created: created) (isAdded, error) in
        if let error = error 
            print(error)
         else 
            print("Data has been added from addCalorieProgress VM")
        
    

如您所见,我只是使用 datamanger 类将数据添加到我的核心数据:

import Foundation
import UIKit
import CoreData

class CoreDataManager 
    
    static let shared: CoreDataManager = 
        let appDelegate = AppDelegate.instance!
        let instance = CoreDataManager(managedObjectContext: appDelegate.persistanceContainer.viewContext)
        return instance
    ()
    
    var managedContext: NSManagedObjectContext
    
  private init(managedObjectContext: NSManagedObjectContext) 
        managedContext = managedObjectContext
    


//MARK:- CalorieTracker Insert/Update/Delete
extension CoreDataManager 

func addCalorieProgressData(id: UUID, calorieProgress: Double, fatProgress: Double, proteinProgress: Double, carbProgress: Double, created: Date, completionHandler: @escaping (_ succeed: Bool, _ error: Error?) -> Void) 
        let calorieProgressEntity = NSEntityDescription.insertNewObject(forEntityName: "CalorieProgressEntity", into: managedContext) as? CalorieProgressEntity
        calorieProgressEntity?.id = id
        calorieProgressEntity?.calorieProgress = calorieProgress
        calorieProgressEntity?.fatProgress = fatProgress
        calorieProgressEntity?.proteinProgress = proteinProgress
        calorieProgressEntity?.carbProgress = carbProgress
        calorieProgressEntity?.created = created

       
        
        do 
            try managedContext.save()
            print("context saved for add calorieGoal")
            completionHandler(true, nil)
         catch let error 
            completionHandler(false, error)
        
    
    
    func fetchCalorieProgressData(completionHandler: @escaping (_ succeed: Any?, _ error: Error?) -> Void) 
        var goals = [CalorieProgressEntity]()
        let calorieGoalRequest: NSFetchRequest<CalorieProgressEntity> = NSFetchRequest<CalorieProgressEntity>(entityName: "CalorieProgressEntity")
        
        do 
            goals = try managedContext.fetch(calorieGoalRequest)
            completionHandler(goals, nil)
         catch let error 
            completionHandler(nil, error)
        
    

任何帮助将不胜感激:)

【问题讨论】:

这不是和你的previous question重复吗? 【参考方案1】:

在 Swift 中有不同的依赖注入方式。您可以通过init() 完成此操作,然后在创建 ViewModel 时传递您的 CoreDataManager。

class CalorieProgressViewModel: ObservableObject 

    @Published var calorieProgress = [CalorieProgressEntity]()
    let manager: CoreDataManager

    init(manager: CoreDataManager) 
        self.manager = manager
    

    //Creating the data
    func addCalorieProgressData(id: UUID, calorieProgress: Double, fatProgress: Double, carbProgress: Double, proteinPogress: Double, created: Date) 
        //<< here don't use your singleton, instead use the manager injected via dependency injection
        manager.addCalorieProgressData(id: id, calorieProgress: calorieProgress, fatProgress: fatProgress, proteinProgress: proteinPogress, carbProgress: carbProgress, created: created) (isAdded, error) in
        if let error = error 

然后,当您创建 ViewModel 时,您将传递 CoreDataManager 作为参数。在这里你可以使用你的 Singleton。

我建议为 CoreDataManager 创建一个协议。然后,您可以轻松地将您的 MockManager 或实际的 CoreDataManager 传递到 ViewModel 以便稍后对其进行测试。

protocol CoreDataManager 
    func addCalorieProgressData(id: UUID, calorieProgress: Double, fatProgress: Double, proteinProgress: Double, carbProgress: Double, created: Date, completionHandler: @escaping (_ succeed: Bool, _ error: Error?) -> Void)



class MainCoreDataManager: CoreDataManager 
    ... 

class MockCoreDataManager: CoreDataManager 
    ... mocked here

【讨论】:

以上是关于如何调整我的视图模型以实现依赖注入 swiftui(用于以后的单元测试)的主要内容,如果未能解决你的问题,请参考以下文章

使用 Unity 进行 MVVM 依赖注入,用于分层视图模型

如何将我的 SwiftUI 视图的 @State 交换为我的视图模型 @Published 变量?

如何将依赖项注入 iOS 视图控制器?

你如何在 UIKit 视图控制器和它呈现的 SwiftUI 视图之间共享数据模型?

swiftui 视图不会更新,直到我最小化应用程序并再次打开它,即使我注入了模型

视图模型中的 Firestore 方法未在 SwiftUI onAppear 方法中调用