- When consulting
topic
aboutunit testing
orautomated test
we often come across very common terms liketestable code
likesynchronous
,predicable
or always return the sameoutput
for differentinput
. However in reality thenet working
code that we often come across is contrary to the terminology we encounter in topics. - Cause us difficulty test code
net working
isnet working
is inherently a work asynchronously and depends heavily on external factors such asinternet connection
,server
, assorted operating systems different . The above factors all directly affect theperforming
,loading
, anddecoding
of thenet work
request
. - In this article we will focus on how to
test
asynchronous
code snippets using theAPI
Foundation
:
1 / Verifying request generation logic:
- When starting a new
test
implementation we should start the reverse by testing the accuracy of the most basiclogic
before moving on totest
theAPI
. - We will both instantiate the
URLRequest
from theEndpoint
with some conditions attached. For testing to have common standards, we createEndpointKind
without customizing:
1 2 3 4 5 6 7 8 9 | <span class="token keyword">extension</span> <span class="token builtin">EndpointKinds</span> <span class="token punctuation">{</span> <span class="token keyword">enum</span> <span class="token builtin">Stub</span> <span class="token punctuation">:</span> <span class="token builtin">EndpointKind</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token keyword">func</span> <span class="token function">prepare</span> <span class="token punctuation">(</span> <span class="token number">_</span> request <span class="token punctuation">:</span> <span class="token keyword">inout</span> <span class="token builtin">URLRequest</span> <span class="token punctuation">,</span> with data <span class="token punctuation">:</span> <span class="token builtin">Void</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// No-op</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- We were able to write the
suite test
with thecode
above where we would need to correctly verify theURLRequest
for the basicendpoint
that don’t needHTTP header
.
1 2 3 4 5 6 7 8 | <span class="token keyword">class</span> <span class="token class-name">EndpointTests</span> <span class="token punctuation">:</span> <span class="token builtin">XCTestCase</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">testBasicRequestGeneration</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> endpoint <span class="token operator">=</span> <span class="token builtin">Endpoint</span> <span class="token operator"><</span> <span class="token builtin">EndpointKinds</span> <span class="token punctuation">.</span> <span class="token builtin">Stub</span> <span class="token punctuation">,</span> <span class="token builtin">String</span> <span class="token operator">></span> <span class="token punctuation">(</span> path <span class="token punctuation">:</span> <span class="token string">"path"</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> request <span class="token operator">=</span> endpoint <span class="token punctuation">.</span> <span class="token function">makeRequest</span> <span class="token punctuation">(</span> with <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token function">XCTAssertEqual</span> <span class="token punctuation">(</span> request <span class="token operator">?</span> <span class="token punctuation">.</span> url <span class="token punctuation">,</span> <span class="token function">URL</span> <span class="token punctuation">(</span> string <span class="token punctuation">:</span> <span class="token string">"https://api.myapp.com/path"</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- We will need to improve the
test case
above, we can get the wrong result because both theURLRequest
we initialized as well asURL
givenURL
can benil
(this is very unlikely but when writetest case
we should not leave out any uncertain cases). - Next we’re assuming that
host
will always beapi.myapp.com
, which go back to the fact that theapp
currently support we usenet working enviroment
asstaging
orproduction
, moreover theapp
also has a fewserver
with thehost
canaddress
different. - We will have the first problem using
XCTUnwrap
to check ifrequest
are not initialized withnil
. We should createtypealias
for specialEndpoint
:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">class</span> <span class="token class-name">EndpointTests</span> <span class="token punctuation">:</span> <span class="token builtin">XCTestCase</span> <span class="token punctuation">{</span> <span class="token keyword">typealias</span> <span class="token builtin">StubbedEndpoint</span> <span class="token operator">=</span> <span class="token builtin">Endpoint</span> <span class="token operator"><</span> <span class="token builtin">EndpointKinds</span> <span class="token punctuation">.</span> <span class="token builtin">Stub</span> <span class="token punctuation">,</span> <span class="token builtin">String</span> <span class="token operator">></span> <span class="token keyword">func</span> <span class="token function">testBasicRequestGeneration</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> endpoint <span class="token operator">=</span> <span class="token function">StubbedEndpoint</span> <span class="token punctuation">(</span> path <span class="token punctuation">:</span> <span class="token string">"path"</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> request <span class="token operator">=</span> <span class="token keyword">try</span> <span class="token function">XCTUnwrap</span> <span class="token punctuation">(</span> endpoint <span class="token punctuation">.</span> <span class="token function">makeRequest</span> <span class="token punctuation">(</span> with <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token function">XCTAssertEqual</span> <span class="token punctuation">(</span> request <span class="token punctuation">.</span> url <span class="token punctuation">,</span> <span class="token function">URL</span> <span class="token punctuation">(</span> string <span class="token punctuation">:</span> <span class="token string">"https://api.myapp.com/path"</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- To be able to control exactly how the
host
works, we use a dedicatedURLHost
to be able to wrap a simpleString
:
1 2 3 4 | <span class="token keyword">struct</span> <span class="token builtin">URLHost</span> <span class="token punctuation">:</span> <span class="token builtin">RawRepresentable</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> rawValue <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">}</span> |
- The benefit of using
URLHost
with specifictype
is that we can encapsulate common variations using staticproperty
in theenum
like how to createproperty
for thehost
likestaging
andproduction
as well asdefault
to dynamically handle it. Reason forapp
cases running inDEBUG
mode
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token keyword">extension</span> <span class="token builtin">URLHost</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token keyword">var</span> staging <span class="token punctuation">:</span> <span class="token keyword">Self</span> <span class="token punctuation">{</span> <span class="token function">URLHost</span> <span class="token punctuation">(</span> rawValue <span class="token punctuation">:</span> <span class="token string">"staging.api.myapp.com"</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">static</span> <span class="token keyword">var</span> production <span class="token punctuation">:</span> <span class="token keyword">Self</span> <span class="token punctuation">{</span> <span class="token function">URLHost</span> <span class="token punctuation">(</span> rawValue <span class="token punctuation">:</span> <span class="token string">"api.myapp.com"</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">static</span> <span class="token keyword">var</span> ` <span class="token keyword">default</span> ` <span class="token punctuation">:</span> <span class="token keyword">Self</span> <span class="token punctuation">{</span> # <span class="token keyword">if</span> <span class="token constant">DEBUG</span> <span class="token keyword">return</span> staging # <span class="token keyword">else</span> <span class="token keyword">return</span> production #endif <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- We need to
update
moreEndpoint
method
initializeURLRequest
byenabling
URLHost
L:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">extension</span> <span class="token builtin">Endpoint</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">makeRequest</span> <span class="token punctuation">(</span> with data <span class="token punctuation">:</span> <span class="token builtin">Kind</span> <span class="token punctuation">.</span> <span class="token builtin">RequestData</span> <span class="token punctuation">,</span> host <span class="token punctuation">:</span> <span class="token builtin">URLHost</span> <span class="token operator">=</span> <span class="token punctuation">.</span> <span class="token keyword">default</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">URLRequest</span> <span class="token operator">?</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> components <span class="token operator">=</span> <span class="token function">URLComponents</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> components <span class="token punctuation">.</span> scheme <span class="token operator">=</span> <span class="token string">"https"</span> components <span class="token punctuation">.</span> host <span class="token operator">=</span> host <span class="token punctuation">.</span> rawValue components <span class="token punctuation">.</span> path <span class="token operator">=</span> <span class="token string">"/"</span> <span class="token operator">+</span> path components <span class="token punctuation">.</span> queryItems <span class="token operator">=</span> queryItems <span class="token punctuation">.</span> <span class="token builtin">isEmpty</span> <span class="token operator">?</span> <span class="token constant">nil</span> <span class="token punctuation">:</span> queryItems <span class="token keyword">guard</span> <span class="token keyword">let</span> url <span class="token operator">=</span> components <span class="token punctuation">.</span> url <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token constant">nil</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> request <span class="token operator">=</span> <span class="token function">URLRequest</span> <span class="token punctuation">(</span> url <span class="token punctuation">:</span> url <span class="token punctuation">)</span> <span class="token builtin">Kind</span> <span class="token punctuation">.</span> <span class="token function">prepare</span> <span class="token punctuation">(</span> <span class="token operator">&</span> request <span class="token punctuation">,</span> with <span class="token punctuation">:</span> data <span class="token punctuation">)</span> <span class="token keyword">return</span> request <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- To avoid having to manually handle each
URL
we use we need to improveURLHost
to make it easier to initialize specialURL
withpath
like this:
1 2 3 4 5 6 7 | <span class="token keyword">extension</span> <span class="token builtin">URLHost</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">expectedURL</span> <span class="token punctuation">(</span> withPath path <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token constant">URL</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> url <span class="token operator">=</span> <span class="token function">URL</span> <span class="token punctuation">(</span> string <span class="token punctuation">:</span> <span class="token string">"https://"</span> <span class="token operator">+</span> rawValue <span class="token operator">+</span> <span class="token string">"/"</span> <span class="token operator">+</span> path <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">try</span> <span class="token function">XCTUnwrap</span> <span class="token punctuation">(</span> url <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- The above
code
not only makes ourtest
more tight, accurate as well as flexible. We can now make thetest case
easier to read and test.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token keyword">class</span> <span class="token class-name">EndpointTests</span> <span class="token punctuation">:</span> <span class="token builtin">XCTestCase</span> <span class="token punctuation">{</span> <span class="token keyword">typealias</span> <span class="token builtin">StubbedEndpoint</span> <span class="token operator">=</span> <span class="token builtin">Endpoint</span> <span class="token operator"><</span> <span class="token builtin">EndpointKinds</span> <span class="token punctuation">.</span> <span class="token builtin">Stub</span> <span class="token punctuation">,</span> <span class="token builtin">String</span> <span class="token operator">></span> <span class="token keyword">let</span> host <span class="token operator">=</span> <span class="token function">URLHost</span> <span class="token punctuation">(</span> rawValue <span class="token punctuation">:</span> <span class="token string">"test"</span> <span class="token punctuation">)</span> <span class="token keyword">func</span> <span class="token function">testBasicRequestGeneration</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> endpoint <span class="token operator">=</span> <span class="token function">StubbedEndpoint</span> <span class="token punctuation">(</span> path <span class="token punctuation">:</span> <span class="token string">"path"</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> request <span class="token operator">=</span> endpoint <span class="token punctuation">.</span> <span class="token function">makeRequest</span> <span class="token punctuation">(</span> with <span class="token punctuation">:</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> host <span class="token punctuation">:</span> host <span class="token punctuation">)</span> <span class="token keyword">try</span> <span class="token function">XCTAssertEqual</span> <span class="token punctuation">(</span> request <span class="token operator">?</span> <span class="token punctuation">.</span> url <span class="token punctuation">,</span> host <span class="token punctuation">.</span> <span class="token function">expectedURL</span> <span class="token punctuation">(</span> withPath <span class="token punctuation">:</span> <span class="token string">"path"</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- We’ve spent quite a bit of
effort
writing custom APIs to improve ourcode base
we can be more robust and flexible. We can create moretest case
to testEndpoint
types quickly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ... func testGeneratingRequestWithQueryItems() throws { let endpoint = StubbedEndpoint(path: "path", queryItems: [ URLQueryItem(name: "a", value: "1"), URLQueryItem(name: "b", value: "2") ]) let request = endpoint.makeRequest(with: (), host: host) try XCTAssertEqual( request?.url, host.expectedURL(withPath: "path?a=1&b=2") ) } } |
- We should create some more
test case
test the realendpoint
to make sure thecode test
working properly. In case we encounter anendpoint
requiresauthentication
, we will need to add anAuthorization
header
when initiating therequest
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">class</span> <span class="token class-name">EndpointTests</span> <span class="token punctuation">:</span> <span class="token builtin">XCTestCase</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token keyword">func</span> <span class="token function">testAddingAccessTokenToPrivateEndpoint</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> endpoint <span class="token operator">=</span> <span class="token builtin">Endpoint</span> <span class="token punctuation">.</span> <span class="token function">search</span> <span class="token punctuation">(</span> <span class="token keyword">for</span> <span class="token punctuation">:</span> <span class="token string">"query"</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> token <span class="token operator">=</span> <span class="token function">AccessToken</span> <span class="token punctuation">(</span> rawValue <span class="token punctuation">:</span> <span class="token string">"12345"</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> request <span class="token operator">=</span> endpoint <span class="token punctuation">.</span> <span class="token function">makeRequest</span> <span class="token punctuation">(</span> with <span class="token punctuation">:</span> token <span class="token punctuation">,</span> host <span class="token punctuation">:</span> host <span class="token punctuation">)</span> <span class="token keyword">try</span> <span class="token function">XCTAssertEqual</span> <span class="token punctuation">(</span> request <span class="token operator">?</span> <span class="token punctuation">.</span> url <span class="token punctuation">,</span> host <span class="token punctuation">.</span> <span class="token function">expectedURL</span> <span class="token punctuation">(</span> withPath <span class="token punctuation">:</span> <span class="token string">"search?q=query"</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token function">XCTAssertEqual</span> <span class="token punctuation">(</span> request <span class="token operator">?</span> <span class="token punctuation">.</span> allHTTPHeaderFields <span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token string">"Authorization"</span> <span class="token punctuation">:</span> <span class="token string">"Bearer 12345"</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
2 / Using integration tests:
- We are currently using the
URLSession
API
inFoundation
in conjunction with someCombine
operator
to build corenet working
code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <span class="token keyword">extension</span> <span class="token builtin">URLSession</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> publisher <span class="token operator"><</span> K <span class="token punctuation">,</span> R <span class="token operator">></span> <span class="token punctuation">(</span> <span class="token keyword">for</span> endpoint <span class="token punctuation">:</span> <span class="token builtin">Endpoint</span> <span class="token operator"><</span> K <span class="token punctuation">,</span> R <span class="token operator">></span> <span class="token punctuation">,</span> using requestData <span class="token punctuation">:</span> K <span class="token punctuation">.</span> <span class="token builtin">RequestData</span> <span class="token punctuation">,</span> decoder <span class="token punctuation">:</span> <span class="token builtin">JSONDecoder</span> <span class="token operator">=</span> <span class="token punctuation">.</span> <span class="token keyword">init</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">AnyPublisher</span> <span class="token operator"><</span> R <span class="token punctuation">,</span> <span class="token builtin">Error</span> <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> request <span class="token operator">=</span> endpoint <span class="token punctuation">.</span> <span class="token function">makeRequest</span> <span class="token punctuation">(</span> with <span class="token punctuation">:</span> requestData <span class="token punctuation">)</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">Fail</span> <span class="token punctuation">(</span> error <span class="token punctuation">:</span> <span class="token function">InvalidEndpointError</span> <span class="token punctuation">(</span> endpoint <span class="token punctuation">:</span> endpoint <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">eraseToAnyPublisher</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token function">dataTaskPublisher</span> <span class="token punctuation">(</span> <span class="token keyword">for</span> <span class="token punctuation">:</span> request <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">map</span> <span class="token punctuation">(</span> <span class="token punctuation">.</span> data <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">decode</span> <span class="token punctuation">(</span> type <span class="token punctuation">:</span> <span class="token builtin">NetworkResponse</span> <span class="token operator"><</span> R <span class="token operator">></span> <span class="token punctuation">.</span> <span class="token keyword">self</span> <span class="token punctuation">,</span> decoder <span class="token punctuation">:</span> decoder <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">map</span> <span class="token punctuation">(</span> <span class="token punctuation">.</span> result <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">eraseToAnyPublisher</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- The
successfully
results of the abovecode
not the ones we should be too concerned with because we use a series of custom systemAPI
that are controlled in some special way to make thetest
results pass. are often narrow and predictable. Protocol
is a method of improvingtest
results not only in the above case but also for many other cases where we do not have to participate in customizing and controllingAPI
. This is a useful and popular method, so in this article we will prioritize the other:- In
Swift
we see thatURLSession
usesURLProtcol
to performnetwork
tasks, the systemApple
has provided as well as complete support for us to customize by usingclass
. That means we can customize aHTTP Net working stack
by ourselves without having to modify customoperator Combine
before. - The downside of
URLProtcol
from my personal point of view is that it mainly relies onstatic method
which means we will have toimplement
ourmock
separately. A temporary fix is to use additional protocolMockURLResponser
to allow us to createmock server
return the necessaryData
orError
:
1 2 3 4 | <span class="token keyword">protocol</span> <span class="token builtin">MockURLResponder</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token keyword">func</span> <span class="token function">respond</span> <span class="token punctuation">(</span> to request <span class="token punctuation">:</span> <span class="token builtin">URLRequest</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Data</span> <span class="token punctuation">}</span> |
- The next thing we need is custom deploy more
URLProtcol
tooverride
themethod
as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | <span class="token keyword">class</span> <span class="token class-name">MockURLProtocol</span> <span class="token operator"><</span> <span class="token builtin">Responder</span> <span class="token punctuation">:</span> <span class="token builtin">MockURLResponder</span> <span class="token operator">></span> <span class="token punctuation">:</span> <span class="token builtin">URLProtocol</span> <span class="token punctuation">{</span> <span class="token keyword">override</span> <span class="token keyword">class</span> <span class="token class-name">func</span> <span class="token function">canInit</span> <span class="token punctuation">(</span> with request <span class="token punctuation">:</span> <span class="token builtin">URLRequest</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Bool</span> <span class="token punctuation">{</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token keyword">override</span> <span class="token keyword">class</span> <span class="token class-name">func</span> <span class="token function">canonicalRequest</span> <span class="token punctuation">(</span> <span class="token keyword">for</span> request <span class="token punctuation">:</span> <span class="token builtin">URLRequest</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">URLRequest</span> <span class="token punctuation">{</span> request <span class="token punctuation">}</span> <span class="token keyword">override</span> <span class="token keyword">func</span> <span class="token function">startLoading</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">guard</span> <span class="token keyword">let</span> client <span class="token operator">=</span> client <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> <span class="token keyword">do</span> <span class="token punctuation">{</span> <span class="token comment">// Here we try to get data from our responder type, and</span> <span class="token comment">// we then send that data, as well as a HTTP response,</span> <span class="token comment">// to our client. If any of those operations fail,</span> <span class="token comment">// we send an error instead:</span> <span class="token keyword">let</span> data <span class="token operator">=</span> <span class="token keyword">try</span> <span class="token builtin">Responder</span> <span class="token punctuation">.</span> <span class="token function">respond</span> <span class="token punctuation">(</span> to <span class="token punctuation">:</span> request <span class="token punctuation">)</span> <span class="token keyword">let</span> response <span class="token operator">=</span> <span class="token keyword">try</span> <span class="token function">XCTUnwrap</span> <span class="token punctuation">(</span> <span class="token function">HTTPURLResponse</span> <span class="token punctuation">(</span> url <span class="token punctuation">:</span> <span class="token function">XCTUnwrap</span> <span class="token punctuation">(</span> request <span class="token punctuation">.</span> url <span class="token punctuation">)</span> <span class="token punctuation">,</span> statusCode <span class="token punctuation">:</span> <span class="token number">200</span> <span class="token punctuation">,</span> httpVersion <span class="token punctuation">:</span> <span class="token string">"HTTP/1.1"</span> <span class="token punctuation">,</span> headerFields <span class="token punctuation">:</span> <span class="token constant">nil</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> client <span class="token punctuation">.</span> <span class="token function">urlProtocol</span> <span class="token punctuation">(</span> <span class="token keyword">self</span> <span class="token punctuation">,</span> didReceive <span class="token punctuation">:</span> response <span class="token punctuation">,</span> cacheStoragePolicy <span class="token punctuation">:</span> <span class="token punctuation">.</span> notAllowed <span class="token punctuation">)</span> client <span class="token punctuation">.</span> <span class="token function">urlProtocol</span> <span class="token punctuation">(</span> <span class="token keyword">self</span> <span class="token punctuation">,</span> didLoad <span class="token punctuation">:</span> data <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">{</span> client <span class="token punctuation">.</span> <span class="token function">urlProtocol</span> <span class="token punctuation">(</span> <span class="token keyword">self</span> <span class="token punctuation">,</span> didFailWithError <span class="token punctuation">:</span> error <span class="token punctuation">)</span> <span class="token punctuation">}</span> client <span class="token punctuation">.</span> <span class="token function">urlProtocolDidFinishLoading</span> <span class="token punctuation">(</span> <span class="token keyword">self</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">override</span> <span class="token keyword">func</span> <span class="token function">stopLoading</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Required method, implement as a no-op.</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- We need
URLSession
to usemock protocol
rather than using the defaultHTTP
system. To do that we just need to letURLSession
useMockURLProtocol
but don’t forget to declare the initialization forURLProtcol
itself:
1 2 3 4 5 6 7 8 9 | <span class="token keyword">extension</span> <span class="token builtin">URLSession</span> <span class="token punctuation">{</span> <span class="token keyword">convenience</span> <span class="token keyword">init</span> <span class="token operator"><</span> T <span class="token punctuation">:</span> <span class="token builtin">MockURLResponder</span> <span class="token operator">></span> <span class="token punctuation">(</span> mockResponder <span class="token punctuation">:</span> T <span class="token punctuation">.</span> <span class="token keyword">Type</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> config <span class="token operator">=</span> <span class="token builtin">URLSessionConfiguration</span> <span class="token punctuation">.</span> ephemeral config <span class="token punctuation">.</span> protocolClasses <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token builtin">MockURLProtocol</span> <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">.</span> <span class="token keyword">self</span> <span class="token punctuation">]</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> <span class="token keyword">init</span> <span class="token punctuation">(</span> configuration <span class="token punctuation">:</span> config <span class="token punctuation">)</span> <span class="token builtin">URLProtocol</span> <span class="token punctuation">.</span> <span class="token function">registerClass</span> <span class="token punctuation">(</span> <span class="token builtin">MockURLProtocol</span> <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">.</span> <span class="token keyword">self</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
- With the above
code
we can now fixed theMockURLResponder
protocol
, for example we will refer to theencode
followingitem
:
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">extension</span> <span class="token builtin">Item</span> <span class="token punctuation">{</span> <span class="token keyword">enum</span> <span class="token builtin">MockDataURLResponder</span> <span class="token punctuation">:</span> <span class="token builtin">MockURLResponder</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token keyword">let</span> item <span class="token operator">=</span> <span class="token function">Item</span> <span class="token punctuation">(</span> title <span class="token punctuation">:</span> <span class="token string">"Title"</span> <span class="token punctuation">,</span> description <span class="token punctuation">:</span> <span class="token string">"Description"</span> <span class="token punctuation">)</span> <span class="token keyword">static</span> <span class="token keyword">func</span> <span class="token function">respond</span> <span class="token punctuation">(</span> to request <span class="token punctuation">:</span> <span class="token builtin">URLRequest</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Data</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> response <span class="token operator">=</span> <span class="token function">NetworkResponse</span> <span class="token punctuation">(</span> result <span class="token punctuation">:</span> item <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">try</span> <span class="token function">JSONEncoder</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">encode</span> <span class="token punctuation">(</span> response <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- We should consider using the
synchronous
logic thatCombine
introduced with usingXCtTest
to buildsynchronous
logic rather than using theGrand Central Dispatch semaphore
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | <span class="token keyword">extension</span> <span class="token builtin">XCTestCase</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> awaitCompletion <span class="token operator"><</span> T <span class="token punctuation">:</span> <span class="token builtin">Publisher</span> <span class="token operator">></span> <span class="token punctuation">(</span> of publisher <span class="token punctuation">:</span> T <span class="token punctuation">,</span> timeout <span class="token punctuation">:</span> <span class="token builtin">TimeInterval</span> <span class="token operator">=</span> <span class="token number">10</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token punctuation">[</span> T <span class="token punctuation">.</span> <span class="token builtin">Output</span> <span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token comment">// An expectation lets us await the result of an asynchronous</span> <span class="token comment">// operation in a synchronous manner:</span> <span class="token keyword">let</span> expectation <span class="token operator">=</span> <span class="token keyword">self</span> <span class="token punctuation">.</span> <span class="token function">expectation</span> <span class="token punctuation">(</span> description <span class="token punctuation">:</span> <span class="token string">"Awaiting publisher completion"</span> <span class="token punctuation">)</span> <span class="token keyword">var</span> completion <span class="token punctuation">:</span> <span class="token builtin">Subscribers</span> <span class="token punctuation">.</span> <span class="token builtin">Completion</span> <span class="token operator"><</span> T <span class="token punctuation">.</span> <span class="token builtin">Failure</span> <span class="token operator">></span> <span class="token operator">?</span> <span class="token keyword">var</span> output <span class="token operator">=</span> <span class="token punctuation">[</span> T <span class="token punctuation">.</span> <span class="token builtin">Output</span> <span class="token punctuation">]</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> cancellable <span class="token operator">=</span> publisher <span class="token punctuation">.</span> sink <span class="token punctuation">{</span> completion <span class="token operator">=</span> $ <span class="token number">0</span> expectation <span class="token punctuation">.</span> <span class="token function">fulfill</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> receiveValue <span class="token punctuation">:</span> <span class="token punctuation">{</span> output <span class="token punctuation">.</span> <span class="token function">append</span> <span class="token punctuation">(</span> $ <span class="token number">0</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// Our test execution will stop at this point until our</span> <span class="token comment">// expectation has been fulfilled, or until the given timeout</span> <span class="token comment">// interval has elapsed:</span> <span class="token function">waitForExpectations</span> <span class="token punctuation">(</span> timeout <span class="token punctuation">:</span> timeout <span class="token punctuation">)</span> <span class="token keyword">switch</span> completion <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token punctuation">.</span> <span class="token function">failure</span> <span class="token punctuation">(</span> <span class="token keyword">let</span> error <span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">throw</span> error <span class="token keyword">case</span> <span class="token punctuation">.</span> finished <span class="token punctuation">:</span> <span class="token keyword">return</span> output <span class="token keyword">case</span> <span class="token constant">nil</span> <span class="token punctuation">:</span> <span class="token comment">// If we enter this code path, then our test has</span> <span class="token comment">// already been marked as failing, since our</span> <span class="token comment">// expectation was never fullfilled.</span> cancellable <span class="token punctuation">.</span> <span class="token function">cancel</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- Like the
Endpoint
test
of the system, we used a lot ofeffort
to build useful tools to be able to deploy thetest
with more concise lines of code. We will conducttest
successfully load
andsuccessfully load
test
therequest
as well asdecode
:
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">class</span> <span class="token class-name">NetworkIntegrationTests</span> <span class="token punctuation">:</span> <span class="token builtin">XCTestCase</span> <span class="token punctuation">{</span> <span class="token keyword">func</span> <span class="token function">testSuccessfullyPerformingRequest</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> session <span class="token operator">=</span> <span class="token function">URLSession</span> <span class="token punctuation">(</span> mockResponder <span class="token punctuation">:</span> <span class="token builtin">Item</span> <span class="token punctuation">.</span> <span class="token builtin">MockDataURLResponder</span> <span class="token punctuation">.</span> <span class="token keyword">self</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> accessToken <span class="token operator">=</span> <span class="token function">AccessToken</span> <span class="token punctuation">(</span> rawValue <span class="token punctuation">:</span> <span class="token string">"12345"</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> publisher <span class="token operator">=</span> session <span class="token punctuation">.</span> <span class="token function">publisher</span> <span class="token punctuation">(</span> <span class="token keyword">for</span> <span class="token punctuation">:</span> <span class="token punctuation">.</span> latestItem <span class="token punctuation">,</span> using <span class="token punctuation">:</span> accessToken <span class="token punctuation">)</span> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token keyword">try</span> <span class="token function">awaitCompletion</span> <span class="token punctuation">(</span> of <span class="token punctuation">:</span> publisher <span class="token punctuation">)</span> <span class="token function">XCTAssertEqual</span> <span class="token punctuation">(</span> result <span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token builtin">Item</span> <span class="token punctuation">.</span> <span class="token builtin">MockDataURLResponder</span> <span class="token punctuation">.</span> item <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
- YEAHH !!!, The last thing we need to take care of is that we can confirm that the
networking
methods work as expected when errors occur:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token keyword">enum</span> <span class="token builtin">MockErrorURLResponder</span> <span class="token punctuation">:</span> <span class="token builtin">MockURLResponder</span> <span class="token punctuation">{</span> <span class="token keyword">static</span> <span class="token keyword">func</span> <span class="token function">respond</span> <span class="token punctuation">(</span> to request <span class="token punctuation">:</span> <span class="token builtin">URLRequest</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">Data</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token function">URLError</span> <span class="token punctuation">(</span> <span class="token punctuation">.</span> badServerResponse <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">NetworkIntegrationTests</span> <span class="token punctuation">:</span> <span class="token builtin">XCTestCase</span> <span class="token punctuation">{</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token keyword">func</span> <span class="token function">testFailingWhenEncounteringError</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">throws</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> session <span class="token operator">=</span> <span class="token function">URLSession</span> <span class="token punctuation">(</span> mockResponder <span class="token punctuation">:</span> <span class="token builtin">MockErrorURLResponder</span> <span class="token punctuation">.</span> <span class="token keyword">self</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> accessToken <span class="token operator">=</span> <span class="token function">AccessToken</span> <span class="token punctuation">(</span> rawValue <span class="token punctuation">:</span> <span class="token string">"12345"</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> publisher <span class="token operator">=</span> session <span class="token punctuation">.</span> <span class="token function">publisher</span> <span class="token punctuation">(</span> <span class="token keyword">for</span> <span class="token punctuation">:</span> <span class="token punctuation">.</span> latestItem <span class="token punctuation">,</span> using <span class="token punctuation">:</span> accessToken <span class="token punctuation">)</span> <span class="token function">XCTAssertThrowsError</span> <span class="token punctuation">(</span> <span class="token keyword">try</span> <span class="token function">awaitCompletion</span> <span class="token punctuation">(</span> of <span class="token punctuation">:</span> publisher <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |