Matchers running slowly
abbeycode opened this issue · comments
I've got a test that's a sort of integration/stress test for my networking code. When I switched from having my networking code check for cached files my tests placed in a temp directory to using Mockingjay, the tests slowed down a lot. I profiled with Instruments, and found that most of the test's time is being spent in MockingjayProtocol.stubForRequest(NSURLRequest) -> Stub?
, and specifically in my custom matcher, which looks like this:
public func urlPathEquals(path: String) -> (request:NSURLRequest) -> Bool {
return { request in
request.URL?.path == path
}
}
Before this test case runs, I loop through my large number of JSON files and create a matcher like this for each one, so there is a large number of mocks configured. What can I do to speed this up? My test is taking so long that it's causing a failure on my build server.
Hi @abbeycode,
Can you please elaborate on roughly how many mocks are configured?
@kylef I've got 7227… I know that's a lot…
Okay, so looping over 7227 different matchers multiple times is probably a little too much. Since they're all paths, I wonder if we can perhaps override stubForRequest
to use a dictionary of URI paths. Then the lockup should be considerably faster.
Something along the lines of:
var stubs: [String: Builder] = [:]
class CustomProtocol : MockingjayProtocol {
class func addStub(path: String, builder: Builder) {
stubs[path] = builder
}
class func stubForRequest(request: NSURLRequest) -> Stub? {
if let path = request.URL?.path, builder = stubs[path] {
return /* some stub for the builder */
}
return nil
}
}
This would certainly need some changes to Mockingjay to provide a more customisable API, but I think it should be possible.
I don't have much time to look at it, but I'll see if I can build a proof of concept tomorrow.
@kylef That's an awesome idea! I thought using a dictionary in this case might make sense also. Thanks for taking a look!
@kylef Thinking about it a little more, if we override stubForRequest
, it would have to somehow be limited to this one stress-test scenario, since I still use other matchers elsewhere, in other test cases.
@abbeycode Okay, what about registering all of the path based matchers in one go.
var paths: [String: NSData] = [:]
func matchPath(request: NSURLRequest) -> Bool {
let path = request.URL?.path
return paths[path] != nil
}
func pathBuilder() {
let path = request.URL?.path
let body = paths[path]!
/* get content for path and return it */
}
stub(matchPath, pathBuilder)
This means no changes to Mockingjay itself and you can still use other matchers.
@kylef thanks! I got a working solution using your last suggestion. My stress test went from taking over 2 minutes to under 10 seconds! For reference, this is the full XCTest extension:
import Foundation
import XCTest
import Mockingjay
private var paths = [String:NSData]()
// MARK: - More efficient stubbing for large numbers of mocks whose path is known up front
public extension XCTest {
func matchPath(request: NSURLRequest) -> Bool {
let path = request.URL!.path!
return paths[path] != nil
}
func pathBuilder(request: NSURLRequest) -> Response {
let path = request.URL!.path!
let body = paths[path]!
return jsonData(body)(request: request)
}
internal func stubByPath(path: String, data: NSData) {
paths[path] = data
self.stub(matchPath, builder: pathBuilder)
}
}