Reputation: 4222
I am trying to unit test a class that has different modes of setup.
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()
}
}
}
}
Both of these will update the models
array with different quantity of data.
What I have right now:
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?
}
This checks if the mode is correct but I am trying to go one step further and check if the correct number of items is setup depending on the mode. And want to call the fetchInfo()
method directly from the XCTestCase
and just validate the model count.
All the tutorials and guides I have seen just talk about faking the behaviour with a URLSession
. But the API call is dependent on the mode that happens as an internal check inside the fetchInfo
method and is the only method exposed to other classes. I would simply like to test the method (in case something breaks inside that method causing a bug).
How do I go about doing that? I can't figure out how to complete the testAPISetup()
method.
Upvotes: 2
Views: 1064
Reputation: 4222
What I had for networking:
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)
}
}
I had to change the above and removed all mentions of static and singletons. I decided to go ahead with using class inheritance. I tried to avoid it and use protocols but it frankly was quite easier to use classes!
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
}
}
And in my test class:
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)
}
I couldn't figure out any other way without having to instantiate an object for APIManager, so had to refactor!
Upvotes: 1