SMock - рдпреВрдирд┐рдЯ-рдкрд░реАрдХреНрд╖рдгреЛрдВ рдХреЗ рд▓рд┐рдП рд╕реНрд╡рд┐рдлреНрдЯ рдореЙрдХрд┐рдВрдЧ рдлреНрд░реЗрдорд╡рд░реНрдХ (рд╡рд┐рдЪрд╛рд░реЛрдВ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж gMock)

рдореБрд╕реАрдмрдд


рдУрдмреАрдЬреАрд╕реА / рд╕реА ++ рд╕реЗ рд╕реНрд╡рд┐рдлреНрдЯ рдХреА рджреБрдирд┐рдпрд╛ рдореЗрдВ рдХрджрдо рд░рдЦрддреЗ рд╣реБрдП, рдореИрдВ рдЗрдХрд╛рдИ рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦрддреЗ рд╕рдордп рдПрдХ рд╕рдорд╕реНрдпрд╛ рдореЗрдВ рднрд╛рдЧ рдЧрдпрд╛: рдЬреЙрдХ рдСрдмреНрдЬреЗрдХреНрдЯ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдкрдХрд░рдгреЛрдВ рдХреА рдХрдореАред


рд╡рд┐рдШрдЯрд┐рдд рдХреЛрдб рд▓рд┐рдЦрддреЗ рд╕рдордп, рд╣рдо рдЕрдХреНрд╕рд░ рдЗрдВрдЯрд░рдлреЗрд╕ (рдкреНрд░реЛрдЯреЛрдХреЙрд▓) рдХреЗ рдкреАрдЫреЗ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рд╡рд┐рд╡рд░рдг рдЫрд┐рдкрд╛рддреЗ рд╣реИрдВред рдореЛрдХрд╛рдореА рдХреЗ рд╕рд╛рде рдЗрд╕рдХреЗ рдШрдЯрдХ рднрд╛рдЧреЛрдВ рдХреА рдЬрдЧрд╣, рджреВрд╕рд░реЛрдВ рд╕реЗ рдЕрд▓рдЧ рд╕реЗ рдХрд┐рд╕реА рд╡рд╕реНрддреБ рдХреА рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХреА рдЬрд╛рдВрдЪ рдХрд░рдирд╛ рднреА рдмрд╣реБрдд рд╕реБрд╡рд┐рдзрд╛рдЬрдирдХ рд╣реИред


Googling, рдореБрдЭреЗ рдЧрд┐рддреБрдм рдкрд░ рдХрдИ рд╕реНрд╡рд┐рдлреНрдЯ рдореЙрдХрд┐рдВрдЧ рдлреНрд░реЗрдорд╡рд░реНрдХ рдорд┐рд▓реЗред рд▓реЗрдХрд┐рди рдЙрдирдореЗрдВ рд╕реЗ рдХреЛрдИ рднреА рдореБрдЭреЗ рд╕реНрдкрд╖реНрдЯ рдФрд░ рд╕реНрдкрд╖реНрдЯ рд░реВрдк рд╕реЗ рдЙрдкрдпреЛрдЧ рдореЗрдВ рдирд╣реАрдВ рдЖрдпрд╛ (рдПрдХ рдпрд╛ рдХрдИ рдХрд╛рд░рдгреЛрдВ рд╕реЗ):


  • рдирд┐рд░реНрднрд░рддрд╛ рдХрд╛ рдПрдХ рдмрд╛рджрд▓ рд╣реИ
  • рдХреЛрдИ рд╕рд╣рдЬ рд╡рд╛рдХреНрдпрд╡рд┐рдиреНрдпрд╛рд╕ рдирд╣реАрдВ
  • рдмрдбрд╝реА рдорд╛рддреНрд░рд╛ рдореЗрдВ рд╕рд╣рд╛рдпрдХ рдХреЛрдб рдХреА рдХреЛрдб рдкреАрдврд╝реА / рдореИрдиреБрдЕрд▓ рд▓реЗрдЦрди рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрддреА рд╣реИ
  • XCTest рдХреЗ рд╕рд╛рде рдПрдХреАрдХреГрдд рдирд╣реАрдВ рд╣реИ (рдЧреАрд▓рд╛ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдкрд░реАрдХреНрд╖рдг рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ)
  • рдХрдИ рд╕реБрд╡рд┐рдзрд╛рдУрдВ рдХреЛ рдПрдХ рдврд╛рдВрдЪреЗ рдХреЗ рдПрдХ рд╕рдореВрд╣ рдореЗрдВ рдбрдВрдк рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ (рдХреНрдпрд╛ рдФрд░ рдХреНрдпреЛрдВ рдХрд╛ рднреНрд░рдо)
  • рдкреВрд░реНрд╡рдирд┐рд░реНрдзрд╛рд░рд┐рдд / рдЕрдВрддрд░реНрдирд┐рд╣рд┐рдд рдкреНрд░рдХрд╛рд░реЛрдВ рдХреА рд╕реАрдорд┐рдд рд╕рдВрдЦреНрдпрд╛ рдХреЗ рд╕рд╛рде рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП

рдпрд╣ рд╕реНрдерд┐рддрд┐ рдореЗрд░реЗ рд▓рд┐рдП рдЕрдкреНрд░рд┐рдп рдереА, рдФрд░ рд▓рдЧрднрдЧ рдПрдХ рд╕рд╛рд▓ рддрдХ рдореИрдВрдиреЗ рд╡рд░реНрдХрдЖрд░реНрд╕ рдФрд░ рд╕реНрд╡-рдирд┐рд░реНрдорд┐рдд рдореЛрдХ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ред
рд╕реНрд╡-рд▓рд┐рдЦрд┐рдд рдирдХрд▓реА рд╡рд╕реНрддреБрдПрдВ рд╕рд░рд▓ рд╣реИрдВ, рд▓реЗрдХрд┐рди рд╡реЗ


  • рд╣рд░ рдмрд╛рд░ рдЕрд▓рдЧ (рдкрд╣рд┐рдпрд╛ рдХреЛ рд╕реБрджреГрдврд╝ рдХрд░реЗрдВ)
  • рдпрд╛ рд╣рд░ рдмрд╛рд░ (рдХреЙрдкреА-рдкреЗрд╕реНрдЯ)
  • рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕реНрдкрд╖реНрдЯ рдирд╣реАрдВ рд╣реИ

рдлреЗрд╕рд▓рд╛


C ++ gTest / gMock ( Google).
Mock- . , ┬л┬╗ ( ) .


gMock, sMock (Swift Mock) gMock.
https://github.com/Alkenso/sMock


sMock:


  • mock /
  • expectations
  • - mock-
  • XCTest.framework
  • zero-dependency
  • pure Swift

()


sMock


  • Mock , ( )
  • Mock methodCall. "" .

Mocking synchronous method


:


import XCTest
import sMock

//    ,   
protocol HTTPClient {
    func sendRequestSync(_ request: String) -> String
}

//  Mock 
class MockHTTPClient: HTTPClient {
    //   call's mock entity.
    let sendRequestSyncCall = MockMethod<String, String>()

    func sendRequestSync(_ request: String) -> String {
        //  1.    Mock-
        //  2.  non-void   "default"    'Unexpected call' (..     ,   ).
        sendRequestSyncCall.call(request) ?? ""
    }
}

//  ,    
struct Client {
    let httpClient: HTTPClient

    //  ,  HTTP ,    .
    func retrieveRecordsSync() -> [String] {
        let response = httpClient.sendRequestSync("{ action: 'retrieve_records' }")
        return response.split(separator: ";").map(String.init)
    }
}

class ExampleTests: XCTestCase {
    func test_Example() {
        let mock = MockHTTPClient()
        let client = Client(httpClient: mock)

        //     expectation,    'sendRequestSync'  HTTPClient     'request'  "{ action: 'retrieve_records' }".
        //   ,      1     "r1;r2;r3".
        mock.sendRequestSyncCall
            //    expectation (      );
            .expect("Request sent.")
            //  ,   expectation    ,      match
            .match("{ action: 'retrieve_records' }")
            //    ,        (   match)
            .willOnce(
                //  ,    .
                .return("r1;r2;r3"))

        //   expectations,   Client-    .
        let records = client.retrieveRecordsSync()
        XCTAssertEqual(records, ["r1", "r2", "r3"])
    }
}

Mocking synchronous method + mocking asynchonous callback


Mock callback'a, .


//    ,   
protocol HTTPClient {
    func sendRequestSync(_ request: String) -> String
}

//  Mock 
class MockHTTPClient: HTTPClient {
    //   call's mock entity.
    let sendRequestSyncCall = MockMethod<String, String>()

    func sendRequestSync(_ request: String) -> String {
        //  1.    Mock-
        //  2.  non-void   "default"    'Unexpected call' (..     ,   ).
        sendRequestSyncCall.call(request) ?? ""
    }
}

//  ,    
struct Client {
    let httpClient: HTTPClient

    //  ,  HTTP ,    .   
    func retrieveRecordsAsync(completion: @escaping ([String]) -> Void) {
        let response = httpClient.sendRequestSync("{ action: 'retrieve_records' }")
        completion(response.split(separator: ";").map(String.init))
    }
}

class ExampleTests: XCTestCase {
    func test_Example() {
        let mock = MockHTTPClient()
        let client = Client(httpClient: mock)

        //     expectation,    'sendRequestSync'  HTTPClient     'request'  "{ action: 'retrieve_records' }".
        //   ,      1     "r1;r2;r3".
        mock.sendRequestSyncCall
            //    expectation (      );
            .expect("Request sent.")
            //  ,   expectation    ,      match
            .match("{ action: 'retrieve_records' }")
            //    ,        (   match)
            .willOnce(
                //  ,    .
                .return("r1;r2;r3"))

        //    expectation    ( ). 
        //     ,     -  :  'MockClosure' mock entity.
        //     expectation,      1    ( ) ["r1", "r2", "r3"].
        let completionCall = MockClosure<[String], Void>()
        completionCall
            //    expectation (      );
            .expect("Records retrieved.")
            //  ,   expectation    ,      match
            .match(["r1", "r2", "r3"])
            //    ,        (   match).
            //   return value   Void, .return   .
            .willOnce()

        //   expectations,   Client-.
        client.retrieveRecordsAsync(completion: completionCall.asClosure())

        //      expectations (  " "    ).
        sMock.waitForExpectations()
    }
}

Matchers, Actions, Argument capture


sMock (. ):


  • Matchers тАФ , .match: .any ( )
  • Actions: expectation match'e .return, .
  • : expectation .
  • : , unexpectedCall


.
.
: , . .


, Swift .
, sMock . .
"testing with Swift"!


Try sMock on github (with SwiftPackageManager)


All Articles