Published on

๐ŸŽ Swift - URLProtocol

Authors
  • avatar
    Name
    ์ด์ฐฝ์ค€
    Twitter

๋ฐฐ๊ฒฝ

๋„คํŠธ์›Œํ‚น์„ ํ…Œ์ŠคํŠธํ•˜๊ธด ํ•ด์•ผํ•  ๊ฒƒ ๊ฐ™์€๋ฐ.. ์–ด๋–ป๊ฒŒ ํ•˜์ง€...?

์•„..! ํ•™์Šต ์Šคํ”„๋ฆฐํŠธ ๋•Œ URLProtocol์ด๋ž€๊ฑธ ์–ธ๊ธ‰ํ•˜์…จ๋˜ ๊ฒƒ ๊ฐ™์€๋ฐ..!?

๊ณผ์ •

URLRequest to Response

์•ฑ์€ ์‹ค์ œ๋กœ ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์„ ํ•˜๊ธฐ ์œ„ํ•ด ์œ„ ์‚ฌ์ง„๊ณผ ๊ฐ™์€ ๊ณผ์ •์„ ๊ฑฐ์นฉ๋‹ˆ๋‹ค.

  1. URLRequest๋ฅผ ์ƒ์„ฑํ•˜๊ณ 
  2. URLRequest๋ฅผ ์ด์šฉํ•ด URLSession Task๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
  3. ์„œ๋ฒ„์™€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ  ๋ฐ›์œผ๋ฉด
  4. ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์‹ฑํ•ด์„œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ €ํฌ๋Š” ์ด ์ค‘์—์„œ 3๋ฒˆ ๊ณผ์ •์„ Mock ์„œ๋ฒ„๋กœ ๋ฐ”๊พธ๊ณ  ์‹ถ์€๊ฑฐ์ฃ ?

URLSession์„ ๋งŒ๋“ค ๋•Œ ํ•„์š”ํ•œ URLSessionConfiguration์€ URLProtocol๋“ค์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ํ•˜๋Š”๋ฐ์š”,

let configuration: URLSessionConfiguration = .ephemeral
configuration.protocolClasses?.insert(MockURLProtocol.self, at: .zero)

์—ฌ๊ธฐ์— ์ €ํฌ๊ฐ€ ์›ํ•˜๋Š” ์ปค์Šคํ…€ Protocol์„ ๋„ฃ๋Š”๋‹ค๋ฉด?

์ €ํฌ๊ฐ€ ์›ํ•˜๋Š” ๋ฐฉ์‹๋Œ€๋กœ URLSession์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๊ฒ๋‹ˆ๋‹ค!

์ด URLProtocol์ด ๋„คํŠธ์›Œํฌ ์ปค๋„ฅ์…˜์„ ์—ด๊ณ , Request๋ฅผ ์ƒ์„ฑํ•˜๊ณ , Response๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •๊นŒ์ง€ ๋ชจ๋“  ๊ฑธ ์›ํ•˜๋Š” ๋ฐฉ์‹๋Œ€๋กœ ๋งŒ๋“ค์–ด์ค„ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Mocking์— ์œ ์šฉํ•œ ๊ฒƒ์ด์ฃ .

๊ทผ๋ฐ ๋ง๋กœ๋งŒ ๋“ค์–ด๋ณด๋ฉด ํ…Œ์ŠคํŒ…์ด ์•„๋‹ˆ๋ผ ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•  ๋•Œ๋„ ๊ฝค ์œ ์šฉํ•  ์ˆ˜ ์žˆ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ญ๋‹ˆ๋‹ค.

URLProtocol

์–ด๋–ค ๋†ˆ์ธ์ง€๋Š” ์•Œ์•˜์ง€๋งŒ ๊ทธ๋ž˜๋„ ์ •์˜๋Š” ํ•œ ๋ฒˆ ๋ด์ค˜์•ผ๊ฒ ์ฃ ? ๊ณต์‹๋ฌธ์„œ ๊ณ ๊ณ 

ํ”„๋กœํ† ์ฝœ์— ๋”ฐ๋ผ URL ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ณผ์ •์„ ํ•ธ๋“ค๋งํ•˜๋Š” ์ถ”์ƒ ํด๋ž˜์Šค

์ด๋ฆ„์€ Protocol์ธ๋ฐ ์ •์ž‘ ํด๋ž˜์Šค๋„ค์š”..ใ…Ž

์‚ฌ์šฉ๋ฒ•์€ URLProtocol์„ ์ƒ์†๋ฐ›๊ณ , override ๋ฉ”์„œ๋“œ๋ฅผ ๋„ค ๊ฐ€์ง€ ์ž‘์„ฑํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

canInit

์ฃผ์–ด์ง„ URLRequest์— URLProtocol์„ ์ ์šฉํ•  ์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

request ํŒŒ๋ผ๋ฏธํ„ฐ์— ์–ด๋–ค ์กฐ๊ฑด๋ฌธ์„ ๊ฑธ์–ด์„œ true๋‚˜ false๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋ฉด ๋˜๋Š”๋ฐ...

๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ์—๋Š” ๊ทธ๋ƒฅ true๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

canonicalRequest

์—ฌ๊ธฐ์„œ๋Š” ์‚ฌ์šฉํ•  URLRequest๋ฅผ ๋ฐ”๊ฟ”์น˜๊ธฐ ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ์š”..

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๊ทธ๋ƒฅ request๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.

startLoading

์‹ค์ œ๋กœ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์„œ๋“œ์ž…๋‹ˆ๋‹ค.

static var requestHandler: ((URLRequest) throws -> (HTTPURLResponse, Data))?

์ด๋Ÿฐ requestHandler๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด์„œ

guard let handler = MockURLProtocol.requestHandler else {
	XCTFail("Received unexpected request with no handler set")
	return
}

do {
	let (response, data) = try handler(request)
	client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
	client?.urlProtocol(self, didLoad: data)
	client?.urlProtocolDidFinishLoading(self)
} catch {
	client?.urlProtocol(self, didFailWithError: error)
}

์ด๋Ÿฐ์‹์œผ๋กœ ์‚ฌ์šฉํ•ด์ค๋‹ˆ๋‹ค.

stopLoading

์ด ๋ฉ”์„œ๋“œ๋Š” Task๊ฐ€ ์ทจ์†Œ๋˜์–ด์•ผ ํ•  ๋•Œ์˜ ํ–‰๋™๊ฐ•๋ น๋“ค์„ ์ ๋Š” ๊ณณ์ธ๋ฐ์š”,

MockURLProtocol์˜ ๊ฒฝ์šฐ์—๋Š” ์ทจ์†Œ๋ฅผ ๋ชฉ์ ์œผ๋กœ ํ•˜์ง€๋Š” ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋นˆ ์นธ์œผ๋กœ ๋น„์›Œ๋‘์—ˆ์Šต๋‹ˆ๋‹ค.

XCTestCase์—์„œ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

์ด๋ ‡๊ฒŒ MockURLProtocol์„ ์ƒ์„ฑํ–ˆ๋‹ค๋ฉด ์‚ฌ์šฉํ•  ์ค€๋น„๊ฐ€ ๋๋‚œ๊ฒ๋‹ˆ๋‹ค!

override func setUp() {
	URLProtocol.registerClass(MockURLProtocol.self)
	let configuration: URLSessionConfiguration = .ephemeral
	configuration.protocolClasses?.insert(MockURLProtocol.self, at: .zero)
	let session = URLSession(configuration: configuration)
	self.networking = MSNetworking(session: session)
}

setUp์—์„œ ์•ž์„œ ๋ดค๋˜๋Œ€๋กœ URLSessionConfiguration์— MockURLProtocol์„ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น Configuration์œผ๋กœ URLSession์„ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค!

๊ฐ Test Case๋งˆ๋‹ค ๋‹ค๋ฅธ Response๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ฒ ์ฃ ?

MockURLProtocol.requestHandler = { _ **in**
	let response = HTTPURLResponse(url: URL(string: "https://api.codesquad.kr")!,
								   statusCode: 200,
								   httpVersion: nil,
								   headerFields: ["Content-Type": "application/json"])!
	return (response, data)
}

MockURLProtocol์— static์œผ๋กœ ์ •์˜ํ•ด๋‘์—ˆ๋˜ requestHandler๋ฅผ ์—ฌ๊ธฐ์„œ ์ •์˜ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ์›ํ•˜๋Š” response๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋ฐ˜ํ™˜์„ ํ•ด์ฃผ๋ฉด, MockURLProtocol์˜ startLoading ๋ฉ”์„œ๋“œ์—์„œ ํ•ด๋‹น ํ•ธ๋“ค๋Ÿฌ๋กœ ์ฃผ์–ด์ง„ ์‘๋‹ต์„ ๊ณ ์ •์ ์œผ๋กœ ๋‚ด๋†“๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด์ฃ .

๊ฒฐ๊ณผ

Repository๋‹จ์—์„œ Mock์„ ๋งŒ๋“ค์–ด์„œ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ๋ฒ•๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ค„ ์•Œ์•˜๋Š”๋ฐ,

์ด๋ ‡๊ฒŒ ๋„คํŠธ์›Œํ‚น ์ž์ฒด๋ฅผ Mockingํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ๊ฝค ์‹ ๊ธฐํ–ˆ์Šต๋‹ˆ๋‹ค.

Response์˜ statusCode๋‚˜ ํŠน๋ณ„ํ•œ ์‘๋‹ต์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ์•„์ฃผ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋„ค์š”.

์ฐธ๊ณ  ๋ฌธ์„œ

WWDC2018 - Testing Tips & Tricks