Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
| 1 | <?php |
||
| 19 | class ClientSpec extends ObjectBehavior |
||
| 20 | { |
||
| 21 | const BASE_URL = 'http://api.host'; |
||
| 22 | |||
| 23 | function it_is_initializable() |
||
| 24 | { |
||
| 25 | $this->shouldHaveType(Client::class); |
||
| 26 | } |
||
| 27 | |||
| 28 | function let( |
||
| 29 | HttpClient $httpClient, |
||
| 30 | MessageFactory $messageFactory, |
||
| 31 | RequestInterface $tokenRequest, |
||
| 32 | ResponseInterface $tokenResponse, |
||
| 33 | StreamInterface $tokenStream |
||
| 34 | ) { |
||
| 35 | $tokenHeaders = ['Content-Type' => 'application/x-www-form-urlencoded']; |
||
| 36 | $tokenRequestUri = sprintf('%s/login_check', self::BASE_URL); |
||
| 37 | $tokenRawRequest = http_build_query(['api_key' => 'abcd']); |
||
| 38 | $tokenRawResponse = json_encode(['token' => 'efgh']); |
||
| 39 | |||
| 40 | $messageFactory->createRequest('POST', $tokenRequestUri, $tokenHeaders, $tokenRawRequest)->willReturn($tokenRequest); |
||
| 41 | $httpClient->sendRequest($tokenRequest)->willReturn($tokenResponse); |
||
| 42 | $tokenResponse->getStatusCode()->willReturn(200); |
||
| 43 | $tokenResponse->getBody()->willReturn($tokenStream); |
||
| 44 | $tokenStream->__toString()->willReturn($tokenRawResponse); |
||
| 45 | |||
| 46 | $this->beConstructedWith($httpClient, 'abcd', self::BASE_URL, $messageFactory); |
||
| 47 | } |
||
| 48 | |||
| 49 | function it_should_send_get_request( |
||
| 50 | HttpClient $httpClient, |
||
| 51 | MessageFactory $messageFactory, |
||
| 52 | RequestInterface $request, |
||
| 53 | ResponseInterface $response, |
||
| 54 | StreamInterface $stream |
||
| 55 | ) { |
||
| 56 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
| 57 | $rawQuery = http_build_query(['query' => 'test']); |
||
| 58 | $requestUri = sprintf('%s/example?%s', self::BASE_URL, $rawQuery); |
||
| 59 | $rawResponse = json_encode(['foo' => 'bar']); |
||
| 60 | |||
| 61 | $messageFactory->createRequest('GET', $requestUri, $headers, null)->willReturn($request); |
||
| 62 | $httpClient->sendRequest($request)->willReturn($response); |
||
| 63 | $response->getStatusCode()->willReturn(200); |
||
| 64 | $response->getBody()->willReturn($stream); |
||
| 65 | $stream->__toString()->willReturn($rawResponse); |
||
| 66 | |||
| 67 | $httpClient->sendRequest($request)->shouldBeCalled(); |
||
| 68 | |||
| 69 | $this->sendRequest('GET', 'example', ['query' => 'test']); |
||
| 70 | } |
||
| 71 | |||
| 72 | function it_should_send_post_request( |
||
| 73 | HttpClient $httpClient, |
||
| 74 | MessageFactory $messageFactory, |
||
| 75 | RequestInterface $request, |
||
| 76 | ResponseInterface $response, |
||
| 77 | StreamInterface $stream |
||
| 78 | ) { |
||
| 79 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
| 80 | $requestUri = sprintf('%s/example', self::BASE_URL); |
||
| 81 | $rawRequest = http_build_query(['query' => 'test']); |
||
| 82 | $rawResponse = json_encode(['foo' => 'bar']); |
||
| 83 | |||
| 84 | $messageFactory->createRequest('POST', $requestUri, $headers, $rawRequest)->willReturn($request); |
||
| 85 | $httpClient->sendRequest($request)->willReturn($response); |
||
| 86 | $response->getStatusCode()->willReturn(200); |
||
| 87 | $response->getBody()->willReturn($stream); |
||
| 88 | $stream->__toString()->willReturn($rawResponse); |
||
| 89 | |||
| 90 | $httpClient->sendRequest($request)->shouldBeCalled(); |
||
| 91 | |||
| 92 | $this->sendRequest('POST', 'example', ['query' => 'test']); |
||
| 93 | } |
||
| 94 | |||
| 95 | function it_should_ask_for_api_token_once( |
||
| 96 | HttpClient $httpClient, |
||
| 97 | MessageFactory $messageFactory, |
||
| 98 | RequestInterface $firstRequest, |
||
| 99 | ResponseInterface $firstResponse, |
||
| 100 | StreamInterface $firstStream, |
||
| 101 | RequestInterface $secondRequest, |
||
| 102 | ResponseInterface $secondResponse, |
||
| 103 | StreamInterface $secondStream, |
||
| 104 | RequestInterface $tokenRequest |
||
| 105 | ) { |
||
| 106 | // first request |
||
| 107 | $firstHeaders = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
| 108 | $firstRequestUri = sprintf('%s/first', self::BASE_URL); |
||
| 109 | $firstRawResponse = json_encode(['foo' => 'bar']); |
||
| 110 | |||
| 111 | $messageFactory->createRequest('GET', $firstRequestUri, $firstHeaders, null)->willReturn($firstRequest); |
||
| 112 | $httpClient->sendRequest($firstRequest)->willReturn($firstResponse); |
||
| 113 | $firstResponse->getStatusCode()->willReturn(200); |
||
| 114 | $firstResponse->getBody()->willReturn($firstStream); |
||
| 115 | $firstStream->__toString()->willReturn($firstRawResponse); |
||
| 116 | |||
| 117 | $httpClient->sendRequest($firstRequest)->shouldBeCalled(); |
||
| 118 | |||
| 119 | $this->sendRequest('GET', 'first'); |
||
| 120 | |||
| 121 | // second request |
||
| 122 | $secondHeaders = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
| 123 | $secondRequestUri = sprintf('%s/second', self::BASE_URL); |
||
| 124 | $secondRawResponse = json_encode(['foo' => 'bar']); |
||
| 125 | |||
| 126 | $messageFactory->createRequest('GET', $secondRequestUri, $secondHeaders, null)->willReturn($secondRequest); |
||
| 127 | $httpClient->sendRequest($secondRequest)->willReturn($secondResponse); |
||
| 128 | $secondResponse->getStatusCode()->willReturn(200); |
||
| 129 | $secondResponse->getBody()->willReturn($secondStream); |
||
| 130 | $secondStream->__toString()->willReturn($secondRawResponse); |
||
| 131 | |||
| 132 | $httpClient->sendRequest($secondRequest)->shouldBeCalled(); |
||
| 133 | |||
| 134 | $this->sendRequest('GET', 'second'); |
||
| 135 | |||
| 136 | $httpClient->sendRequest($tokenRequest)->shouldHaveBeenCalledTimes(1); |
||
| 137 | } |
||
| 138 | |||
| 139 | function it_should_renew_the_token_and_resend_the_request( |
||
| 140 | HttpClient $httpClient, |
||
| 141 | MessageFactory $messageFactory, |
||
| 142 | RequestInterface $firstRequest, |
||
| 143 | ResponseInterface $firstResponse, |
||
| 144 | StreamInterface $firstStream, |
||
| 145 | RequestInterface $secondRequest, |
||
| 146 | ResponseInterface $secondResponse, |
||
| 147 | StreamInterface $secondStream, |
||
| 148 | RequestInterface $tokenRequest |
||
| 149 | ) { |
||
| 150 | // first request |
||
| 151 | $firstHeaders = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
| 152 | $firstRequestUri = sprintf('%s/first', self::BASE_URL); |
||
| 153 | $firstRawResponse = json_encode(['foo' => 'bar']); |
||
| 154 | |||
| 155 | $messageFactory->createRequest('GET', $firstRequestUri, $firstHeaders, null)->willReturn($firstRequest); |
||
| 156 | $httpClient->sendRequest($firstRequest)->willReturn($firstResponse); |
||
| 157 | $firstResponse->getStatusCode()->willReturn(200); |
||
| 158 | $firstResponse->getBody()->willReturn($firstStream); |
||
| 159 | $firstStream->__toString()->willReturn($firstRawResponse); |
||
| 160 | |||
| 161 | $httpClient->sendRequest($firstRequest)->shouldBeCalled(); |
||
| 162 | |||
| 163 | $this->sendRequest('GET', 'first'); |
||
| 164 | |||
| 165 | // second request |
||
| 166 | $secondHeaders = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
| 167 | $secondRequestUri = sprintf('%s/second', self::BASE_URL); |
||
| 168 | $secondRawResponse = json_encode(['foo' => 'bar']); |
||
| 169 | |||
| 170 | $messageFactory->createRequest('GET', $secondRequestUri, $secondHeaders, null)->willReturn($secondRequest); |
||
| 171 | $secondResponse->getStatusCode()->willReturn(200); |
||
| 172 | $secondResponse->getBody()->willReturn($secondStream); |
||
| 173 | $secondStream->__toString()->willReturn($secondRawResponse); |
||
| 174 | |||
| 175 | $secondRequestCounter = 0; |
||
| 176 | |||
| 177 | $httpClient->sendRequest($secondRequest)->will(function () use ($secondRequestCounter, $secondResponse) { |
||
| 178 | $secondRequestCounter ++; |
||
| 179 | |||
| 180 | if ($secondRequestCounter === 1) { |
||
| 181 | return MessageFactoryDiscovery::find()->createResponse(401); |
||
| 182 | } |
||
| 183 | |||
| 184 | return $secondResponse; |
||
| 185 | }); |
||
| 186 | |||
| 187 | $httpClient->sendRequest($secondRequest)->shouldBeCalledTimes(2); |
||
| 188 | |||
| 189 | $this->shouldThrow(InvalidResponseException::class)->during('sendRequest', ['GET', 'second']); |
||
| 190 | |||
| 191 | $httpClient->sendRequest($tokenRequest)->shouldHaveBeenCalledTimes(2); |
||
| 192 | } |
||
| 193 | |||
| 194 | function it_should_not_renew_the_token( |
||
| 195 | HttpClient $httpClient, |
||
| 196 | MessageFactory $messageFactory, |
||
| 197 | RequestInterface $request, |
||
| 198 | ResponseInterface $response, |
||
| 199 | StreamInterface $stream, |
||
| 200 | RequestInterface $tokenRequest |
||
| 201 | ) { |
||
| 202 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
| 203 | $requestUri = sprintf('%s/example', self::BASE_URL); |
||
| 204 | $rawResponse = json_encode(['foo' => 'bar']); |
||
| 205 | |||
| 206 | $messageFactory->createRequest('GET', $requestUri, $headers, null)->willReturn($request); |
||
| 207 | $httpClient->sendRequest($request)->willReturn($response); |
||
| 208 | $response->getStatusCode()->willReturn(401); |
||
| 209 | $stream->__toString()->willReturn($rawResponse); |
||
| 210 | |||
| 211 | $httpClient->sendRequest($request)->shouldBeCalled(); |
||
| 212 | |||
| 213 | $this->shouldThrow(InvalidResponseException::class)->during('sendRequest', ['GET', 'example']); |
||
| 214 | |||
| 215 | $httpClient->sendRequest($tokenRequest)->shouldHaveBeenCalledTimes(1); |
||
| 216 | } |
||
| 217 | |||
| 218 | function it_should_throw_transfer_exception_on_http_exception( |
||
| 219 | HttpClient $httpClient, |
||
| 220 | MessageFactory $messageFactory, |
||
| 221 | RequestInterface $request |
||
| 222 | ) { |
||
| 223 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
| 224 | $requestUri = sprintf('%s/example', self::BASE_URL); |
||
| 225 | |||
| 226 | $errorResponse = MessageFactoryDiscovery::find()->createResponse(500); |
||
| 227 | $httpException = HttpException::create($request->getWrappedObject(), $errorResponse); |
||
| 228 | |||
| 229 | $messageFactory->createRequest('GET', $requestUri, $headers, null)->willReturn($request); |
||
| 230 | $httpClient->sendRequest($request)->willThrow($httpException); |
||
| 231 | |||
| 232 | $this->shouldThrow(TransferException::class)->during('sendRequest', ['GET', 'example']); |
||
| 233 | } |
||
| 234 | |||
| 235 | function it_should_throw_invalid_response_exception_on_bad_status_code( |
||
| 236 | HttpClient $httpClient, |
||
| 237 | MessageFactory $messageFactory, |
||
| 238 | RequestInterface $request, |
||
| 239 | ResponseInterface $response |
||
| 240 | ) { |
||
| 241 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
| 242 | $requestUri = sprintf('%s/example', self::BASE_URL); |
||
| 243 | |||
| 244 | $messageFactory->createRequest('GET', $requestUri, $headers, null)->willReturn($request); |
||
| 245 | $httpClient->sendRequest($request)->willReturn($response); |
||
| 246 | $response->getStatusCode()->willReturn(400); |
||
| 247 | |||
| 248 | $this->shouldThrow(InvalidResponseException::class)->during('sendRequest', ['GET', 'example']); |
||
| 249 | } |
||
| 250 | |||
| 251 | function it_should_throw_invalid_response_exception_on_broken_response_body( |
||
| 252 | HttpClient $httpClient, |
||
| 253 | MessageFactory $messageFactory, |
||
| 254 | RequestInterface $request, |
||
| 255 | ResponseInterface $response, |
||
| 256 | StreamInterface $stream |
||
| 257 | ) { |
||
| 258 | $headers = ['Content-Type' => 'application/x-www-form-urlencoded', 'Authorization' => 'Bearer efgh']; |
||
| 259 | $requestUri = sprintf('%s/example', self::BASE_URL); |
||
| 260 | |||
| 261 | $messageFactory->createRequest('GET', $requestUri, $headers, null)->willReturn($request); |
||
| 262 | $httpClient->sendRequest($request)->willReturn($response); |
||
| 263 | $response->getStatusCode()->willReturn(200); |
||
| 264 | $response->getBody()->willReturn($stream); |
||
| 265 | $stream->__toString()->willReturn('it is not a json'); |
||
| 266 | |||
| 267 | $this->shouldThrow(InvalidResponseException::class)->during('sendRequest', ['GET', 'example']); |
||
| 268 | } |
||
| 269 | |||
| 270 | function it_should_throw_authenfication_exception_on_http_exception( |
||
| 271 | HttpClient $httpClient, |
||
| 272 | RequestInterface $tokenRequest |
||
| 273 | ) { |
||
| 274 | $errorResponse = MessageFactoryDiscovery::find()->createResponse(500); |
||
| 275 | $httpException = HttpException::create($tokenRequest->getWrappedObject(), $errorResponse); |
||
| 276 | |||
| 277 | $httpClient->sendRequest($tokenRequest)->willThrow($httpException); |
||
| 278 | |||
| 279 | $this->shouldThrow(AuthenficationException::class)->during('sendRequest', ['GET', 'example']); |
||
| 280 | } |
||
| 281 | |||
| 282 | function it_should_throw_authenfication_exception_on_bad_status_code(ResponseInterface $tokenResponse) |
||
| 283 | { |
||
| 284 | $tokenResponse->getStatusCode()->willReturn(400); |
||
| 285 | |||
| 286 | $this->shouldThrow(AuthenficationException::class)->during('sendRequest', ['GET', 'example']); |
||
| 287 | } |
||
| 288 | |||
| 289 | function it_should_throw_authenfication_exception_on_broken_response_body(StreamInterface $tokenStream) |
||
| 290 | { |
||
| 291 | $tokenStream->__toString()->willReturn('it is not a json'); |
||
| 292 | |||
| 293 | $this->shouldThrow(AuthenficationException::class)->during('sendRequest', ['GET', 'example']); |
||
| 294 | } |
||
| 295 | |||
| 296 | function it_should_throw_authenfication_exception_on_bad_response_body(StreamInterface $tokenStream) |
||
| 297 | { |
||
| 298 | $rawResponse = json_encode(['it' => 'is not a token']); |
||
| 299 | |||
| 300 | $tokenStream->__toString()->willReturn($rawResponse); |
||
| 301 | |||
| 302 | $this->shouldThrow(AuthenficationException::class)->during('sendRequest', ['GET', 'example']); |
||
| 303 | } |
||
| 304 | } |
||
| 305 |