Why? Over the past week, I have been refactoring our networking layer here at WeVat. The unit test coverage for the error scenarios that may arise from the was pretty low, so I thought that it was a good opportunity to cast a bigger net and assure that our network refactor didn’t introduce any pesky 🐞, 🕷 or 🐜s. network Below I will briefly explain the unit test suite we are using, and will then move onto how we are dynamically testing multiple error scenarios without writing lots of code. Approach OHHTTPStubs I have used on a previous project, and it seems to be the industry standard as a network stubbing library for iOS. It provides a suite of tools to help stub out any network calls that are made using or , so works great for our needs here. OHTTPStubs NSURLConnection, NSURLSession Alamofire I have also created a small wrapper around the library, to provide all of the stubbing code to us, in exactly the way we need it. The method below gives us the ability to stub the network with a 200 and a defined response. JSON static func stubNetwork(forService service: Service.Type) { stub(condition: isHost(baseUrl)) { \_ in guard let path = OHPathForFileInBundle(stubPath(forService: service), Bundle.main) else { preconditionFailure(“Stub not found!”) } return OHHTTPStubsResponse( fileAtPath: path, statusCode: 200, headers: \[ “Content-Type”: “application/json” \] ) } } I’ll explain what is happening here We pass in the type of service we want to stub. Service is a protocol, which all of our services conform to. This gives the ability to switch over the Service.Type and provide the stub data we need for each service. We give the stub a condition. This will be the host url that we want to stub. It will stub any request made with this url. We get the local file path which holds the JSON data for the response. Return this file, stubbing all requests made with this url until the stub is removed using . OHHTTPStubs.removeAllStubs() Nimble We chose to cut down on using some of the more verbose syntax and coding structure that is provided by XCTest, and went with using the framework. Nimble This allowed us to go from setting up expectations with let expect = expectation(description: “Get Receipts”)...expect.fulfill()...waitForExpectations(timeout: 2) { (error) inXCTAssertTrue(asyncReceipts?.count > 0)} to simply: expect(asyncReceipts!.count > 0).toEventually(beTrue()) 👍🏻👍🏻👍🏻 Testing 🙂 path This should be proven in a single test case. Just set up the success stub, call the service and assert the async result has been fulfilled. var asyncReceipts: [Receipts]?StubHelper.stubNetwork(forService: GetReceiptsService.self) GetReceiptsService.getReceipts(forUser: UUID().uuidString) { (result) in switch result { case .success(let receipts): asyncReceipts = receipts default: break } } expect(asyncReceipts).toEventuallyNot(beNil())expect(asyncReceipts!.count > 0).toEventually(beTrue()) Testing 🙁 paths This is where the unit tests really start to prove their worth. We can similarly write a test case for the network call, which will set up the stub for the error code we want to test, call the service, and assert that the errors are being set asynchronously. var asyncError: Error?stubNetworkWithNoConnection() GetReceiptsService.getReceipts(forUser: “”) { (result) in switch result { case .failure(let error): asyncError = error default: break } } expect(asyncError).toEventuallyNot(beNil())expect(asyncError as! HTTPError).toEventually(equal(HTTPError.noConnection)) That’s cool. But I hear you, this seems like it can be optimised. We don’t want to repeat this code every time we want to test for a different error. Let’s Protocolise So thinking about our requirements here, we need to test for every likely error response from the server, on each of our services. Also we need to assert that the errors coming back are what we expect. ServiceTestable protocol protocol ServiceTestable { func testFailures() func setupNetworkFailureTest(withStub stub: (() -> Void), andErrorToAssert errorAssertion: HTTPError) } Here I have defined a protocol, which will provide a harness for how we test our service classes. By ensuring our services conform to this protocol, we can call the which will call out to the function, for each scenario we need. We then inject the stub and the error we want to assert into the test. testFailures() setupNetworkFailureTest() Now where is the implementation you ask? Hold up a second and I will tell you! Enter protocol extensions By extending our protocol, we can give some default behaviour to our test cases. ServiceTestable extension ServiceTestable where Self: WeVatTests { func testFailuresWithStatusCodes() {let failures = [ (stub: StubHelper.stubNetworkWithNotFound, error: HTTPError.notFound), (stub: StubHelper.stubNetworkWithForbidden, error: HTTPError.invalidCredentials), (stub: StubHelper.stubNetworkTimeout, error: HTTPError.timeout), (stub: StubHelper.stubNetworkNoConnection, error: HTTPError.noConnection), (stub: StubHelper.stubNetworkWithBadRequest, error: HTTPError.invalidRequest), (stub: StubHelper.stubNetworkWithServerError, error: HTTPError.serverError), (stub: StubHelper.stubNetworkWithUnauthorized, error: HTTPError.invalidCredentials), (stub: StubHelper.stubNetworkWithServerTimeout, error: HTTPError.timeout) \] for failure in failures { setupNetworkFailureTest(withStub: failure.stub, andErrorToAssert: failure.error) } } } This looks like a big old block o’ code, but what it will provide is pretty great. We set up an array of tuples, each array element holding A reference to the Stub we want to use. The error that we will assert against. Now the classes that will conform to our protocol will look something like this: ServiceTestable class GetReceiptsServiceTests: WeVatTests, ServiceTestable { override func tearDown() { super.tearDown() StubHelper.unStubNetwork() } ... func testFailures() { testFailuresWithStatusCodes() } func setupNetworkFailureTest(withStub stub: (() -> Void), andErrorToAssert errorAssertion: HTTPError) { var asyncError: Error? stub() GetReceiptsService.getReceipts(forUser: UUID().uuidString) { (result) in switch result { case .failure(let error): asyncError = error default: break } } expect(asyncError).toEventuallyNot(beNil()) expect(asyncError as! HTTPError).toEventually(equal(errorAssertion)) } } I’ll walk you through what is now going on: We now ensure the service test classes are conforming to protocol ServiceTestable is being called by XCTest when running our unit test suite testFailures() We call out to in our protocol extension. testFailuresWithStatusCodes() The extension does the leg work in calling through to our implementation in for each of the errors we want. setupNetworkFailureTest Wrap up What we have now, is an extendable test harness for our service layer. When we add another service, it is easy to hook it up to the ServiceTestable protocol, which will ensure that all error cases are tested fully. If we want to add further error scenarios, we can just append them to the array and a reference to their corresponding error stub. is how hackers start their afternoons. We’re a part of the family. We are now and happy to opportunities. Hacker Noon @AMI accepting submissions discuss advertising & sponsorship To learn more, , , or simply, read our about page like/message us on Facebook tweet/DM @HackerNoon. If you enjoyed this story, we recommend reading our and . Until next time, don’t take the realities of the world for granted! latest tech stories trending tech stories