带有内部开关盒的单元测试 API 调用
Posted
技术标签:
【中文标题】带有内部开关盒的单元测试 API 调用【英文标题】:Unit test API Call with internal switch case 【发布时间】:2020-08-13 23:40:24 【问题描述】:我正在尝试对具有不同设置模式的类进行单元测试。
class Controller
enum Mode
case listing
case pages(String?)
private (set) var mode : Mode = .listing
private (set) var models = [Model]()
init()
...
init(id : String)
mode = .pages(id)
func fetchInfo()
switch mode
case .listing:
ApiManager.firstNetworkCall() (json, error) in ...
setupModel()
case .pages(let id):
ApiManager.secondNetworkCall(id : id) (json, error) in ...
setupModel()
这两个都会用不同数量的数据更新models
数组。
我现在拥有的:
var controller : Controller!
override func setUpWithError() throws
// Put setup code here. This method is called before the invocation of each test method in the class.
try super.setUpWithError()
controller = Controller()
override func tearDownWithError() throws
// Put teardown code here. This method is called after the invocation of each test method in the class.
controller = nil
try super.tearDownWithError()
func testDefaultListingMode() throws
switch controller.mode
case .listing:
XCTAssertTrue(true)
default:
XCTAssertFalse(false)
func testAPISetup() throws
controller.fetchInfo()
//now what?
这会检查模式是否正确,但我试图更进一步,检查是否根据模式设置了正确数量的项目。并想直接从XCTestCase
调用fetchInfo()
方法并验证模型计数。
我看到的所有教程和指南都只是谈论用URLSession
伪造行为。但是 API 调用依赖于在 fetchInfo
方法中作为内部检查发生的模式,并且是唯一暴露给其他类的方法。我只是想测试该方法(以防该方法内部出现问题导致错误)。
我该怎么做呢?我不知道如何完成testAPISetup()
方法。
【问题讨论】:
您的控制器(及其测试)做得太多了。您的控制器的测试不应该测试任何有关 API(或它返回的内容)如何工作的内容。您应该只测试控制器是否调用了正确的 API(使用假实现)。从那里,编写单独的测试以确保 API 正常工作。 @Alexander-ReinstateMonica 我试图使用返回的数据量来测试是否调用了正确的 API。没有设置其他变量可以让我区分这两个响应。您是否建议仅使用URLSession
单独测试 API 而忘记“模式”?因为“你应该只测试控制器调用正确的 API”这正是我坚持的部分!
啊,我知道是什么绊倒了你。不,不要使用结果来确定正在调用两个网络 API 中的哪一个(这在一般情况下不起作用,如果您有一个没有返回结果的依赖项怎么办?)。 Extract the interface 的网络客户端(我猜你可能很快将其称为“提取协议”),将您的网络客户端作为 VC 的依赖项注入,然后您的测试代码可以使用该协议的模拟实现,该协议检测哪个方法被调用
"我必须将整个 APIManager 作为参数传递。" VC 与“普通”对象有点不同,因为它们有两个阶段初始化,它们通常由系统/Storyboard 分配和最小初始化,然后从viewDidLoad
和朋友进一步“初始化”以配置您的非视图相关属性。所以你会使用类似于设置注入而不是构造函数注入的东西。您可以通过 segue 注入依赖项,或者使用默认值(您的单元测试可以覆盖)
@Rikh 你刚刚发现了为什么对全局/静态成员的引用会使测试变得困难:)
【参考方案1】:
我对网络的了解:
class NetworkingManager
static var alamoFireManager = Session.default
static func POST(...., completion : ()->())
sendRequest(....., completion : completion)
private static func sendRequest(...., completion : ()->())
let request = alamoFireManager.request(.....)
request.responseJSON
completion()
class APIManager
static func firstNetworkCall(completion : ()->())
NetworkingManager.POST(..., completion : completion)
我不得不更改上述内容并删除所有提及静态和单例的内容。我决定继续使用类继承。我试图避免它并使用协议,但坦率地说,使用类更容易!
class NetworkingManager
private (set) var sessionManager: Session
init(config : URLSessionConfiguration = .default)
config.timeoutIntervalForResource = 8.0
config.timeoutIntervalForRequest = 8.0
self.sessionManager = Session(configuration: config)
func request(...)
//hit alamofire
class APIManager : NetworkingManager
override init(config: URLSessionConfiguration = .default)
super.init(config: config)
//other methods
...
class Controller
private let apiManager : APIManager
init(manager : APIManager = APIManager())
self.apiManager = manager
在我的测试课中:
override func setUpWithError() throws
// Put setup code here. This method is called before the invocation of each test method in the class.
try super.setUpWithError()
let config = URLSessionConfiguration.ephemeral
apiManager = APIManager(config : config)
controller = Controller(manager : apiManager)
func testApiCalled() throws
controller.fetchNecessaryInfo()
//had to add one second delay as alamofire adds the request on another queue. Wasn't able to put it on main queue.
sleep(1)
let promise = expectation(description: "Check request called")
apiManager.sessionManager.session.getAllTasks (taskArray) in
if taskArray.count > 1
XCTFail("Multiple requests when there should be only one")
if let task = taskArray.first, let request = task.currentRequest
if let string = request.url?.absoluteString
XCTAssert(...)
else
XCTFail("Incorrect URL")
else
XCTFail("Somehow no task exists. So this is an error")
promise.fulfill()
wait(for: [promise], timeout: 1.0)
如果不为 APIManager 实例化一个对象,我想不出任何其他方法,所以不得不重构!
【讨论】:
以上是关于带有内部开关盒的单元测试 API 调用的主要内容,如果未能解决你的问题,请参考以下文章