Pruebas unitarias de tráfico HTTP en la aplicación Alamofire


Estoy luchando un poco para averiguar cómo probar mejor una aplicación que utiliza Alamofire para ayudar a sincronizar con los datos del servidor.

Quiero poder probar mi código que usa Alamofire y procesa respuestas JSON desde un servidor. Me gustaría simular esas pruebas para poder alimentar los datos de respuesta esperados a esas pruebas sin incurrir en tráfico de red real.

Esta entrada del blog ( http://nshipster.com/xctestcase / ) describe lo fácil que es burlarse de un objeto en Swift - pero no estoy seguro cómo hacer eso con Alamofire y sus respuestas encadenadas.

¿Me burlaría del Gerente? la Solicitud? Respuesta? Cualquier ayuda sería apreciada!

Author: Daniel D, 2014-11-14

3 answers

Estoy agregando otra respuesta ya que acabo de encontrar este enfoque que en mi opinión es más fácil y realmente simple de leer y usar.

He creado una clase falsa Alamofire que contiene solo las funciones y los tipos necesarios para las pruebas. Ahora incluyo este archivo en el objetivo de prueba en lugar del Alamofire real.

Por ejemplo, he creado mi versión de la clase Request donde defino un par de variables estáticas que valorizo dependiendo de la prueba, y para esta clase he implementadas solo las funciones init y responseJSON.

public class Request {

    var request:String?
    struct response{
        static var data:NSHTTPURLResponse?
        static var json:AnyObject?
        static var error:NSError?
    }

    init (request:String){
        self.request = request
    }

    public func responseJSON(options: NSJSONReadingOptions = .AllowFragments, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {

        completionHandler(NSURLRequest(URL: NSURL(string:self.request!)!), Request.response.data, Request.response.json, Request.response.error)
        return self
    }
}

Ahora puedo burlarme de una respuesta en una prueba:

func testMytestFunction(){
    var HTMLResponse = NSHTTPURLResponse(URL: NSURL(string: "myurl")!, statusCode: 200, HTTPVersion: "HTTP/1.1", headerFields: nil)

    Request.response.data = HTMLResponse
    Request.response.json = LoadDataFromJSONFile("MyJsonFile")

    request(.POST, "myurl", parameters: nil, encoding: ParameterEncoding.JSON).responseJSON {
        (request, response, JSON, error) -> Void in
        // the JSON and response variable now contains exactly the data that you have passed to Request.response.data and Request.response.json
    }
}

La función request se define aquí:

public func request(method: Method, URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request {

    return Request(request: URLString.URLString)
}

public func request(URLRequest: URLRequestConvertible) -> Request {

    return Request(request: "fakecall")
}
 20
Author: MatterGoal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-18 08:13:12

Esta pregunta se está haciendo vieja, pero acabo de encontrar el mismo problema, y la solución es muy fácil cuando se utiliza OHHTTPStubs.

OHHTTPStubs solo se burla de las respuestas que obtienes de NSURLSession, por lo que funciona bien con Alamofire, y obtienes una cobertura muy buena de la ruta de tu código.

Por ejemplo, en su caso de prueba, simplemente simule la respuesta usando:

OHHTTPStubs.stubRequestsPassingTest({
  (request: NSURLRequest) -> Bool in
    return request.URL!.host == "myhost.com"
  }, withStubResponse: {
  (request: NSURLRequest) -> OHHTTPStubsResponse in
    let obj = ["status": "ok", "data": "something"]
    return OHHTTPStubsResponse(JSONObject: obj, statusCode:200, headers:nil)
})
 9
Author: user541160,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-22 22:06:58

Esperando una respuesta de @mattt posteo un ejemplo de mi código.

Digamos que tenemos una clase Client que es responsable de llamar a un servicio web simple. Esta clase implementa una función llamada userSignIn que realiza un inicio de sesión usando el WS.

Este es el código para la función userSignIn:

func userSignIn(
        #email:String,
        password:String,
        completionHandler: (Bool, String?, NSError?) -> Void
        )-> Void
        {

            var parameters:[String:AnyObject] = [
                "email":email,
                "password":password,
            ]


            Alamofire.request(.POST, Client.urlPath, parameters: parameters, encoding: ParameterEncoding.JSON).responseJSON {
                (request, response, JSON, responseError) -> Void in

                // Setup callback params

                // HERE WE INJECT THE "FAKE" DATA--------
                var operationComplete = false
                var accessToken:String?
                var error:NSError?
                // --------------------------------------

                if let statusCode = response?.statusCode {

                    // Check for errors and build response data
                    (operationComplete, accessToken, error) = self.checkSignInResponse(statusCode, JSON: JSON)
                }

                // Call the completion handler
                completionHandler(operationComplete, accessToken, error)
            }
    }

El objetivo de la función es obtener un token del servicio web si la información pasada por el usuario es correcta.

La función checkSignInResponse (No informo su código ya que no es útil para la respuesta) tiene el papel de valorizar las 3 variables operationComplete, accessToken y error dependiendo de la respuesta JSON recibida.

Ahora que las 3 variables tienen un valor, las llamamos completionHandler usándolas.

Cómo burlarse de esta función?!

Para simular la respuesta sobrescribo la función userSignIn directamente en la función de prueba (como se explica en el artículo de NSHipster).

func testUserSignIn_whenParamsAreInvalid(){

    class MockClient:Client {

        override func userSignIn(#email: String, password: String, completionHandler:
            (Bool, String?, NSError?) -> Void) {

            // Set callback params
            var operationComplete = false
            var accessToken:String? = nil
            var error:NSError? = NSError(domain: "Testing", code: 99, userInfo: nil)

            completionHandler(operationComplete, accessToken, error)
        }
    }

    signInViewController!.client = MockClient()
    signInViewController!.loadView()

    fillRegisterFieldsWithDataAndSubmit(femail(), password: fpassword())

    XCTAssertNotNil(signInViewController!.error, "Expect error to be not nil")

}

Entonces sustituyo el client dentro de la vista controlador que estoy probando usando mi cliente "burlado". En este caso estoy probando que el controlador pasa a la información de la función que no es válida así que compruebo que la propiedad error del controlador no es nil. Para forzar estos datos simplemente establecí operationComplete a false y generé manualmente un NSError.

¿Tiene algún sentido para ti? No estoy seguro de que esta prueba sea una buena prueba... pero al menos puedo verificar el flujo de datos.

 1
Author: MatterGoal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-23 09:21:12