The Recording HTTP
Proxy: Not Yet Another
Messiah
Viktor Todorov
About me
● Sofia, Bulgaria
● PHP Since 2009
● Software developer at Siteground since 2017
● Heavy Metal heart
● History
● Beer
● Japanese Anime
About Me
● Symfony
● NoSQL (Cassandra DB, Redis, Apache Solr/Lucene)
● Automation Testing Freak
Recording HTTP Proxy WireMock Measure The Results
Recording HTTP Proxy WireMock Measure The Results
Recording HTTP Proxy WireMock Measure The Results
Part I
Software Testing
● Software quality is the responsibility of every team member, especially the developers
● Automation is a great way to improve the quality of your software
● Unit Tests are Good, But Not Perfect. Especially for Legacy Application
Issues with Unit/Integration Testing a Legacy Application
● Quite a Big Project
● The Code Is Not Testable
● Where to Start?
● What kind of tests?
● Long Period Until Results
● Management Issues
Testing Legacy Application
What should we do?
I don’t know :(
Investigate: Find out how Big Companies are doing it
Recording HTTP Proxy
Software Testing Pyramid
E2E Testing
Definition
End-to-end testing is a Software testing methodology to test an application flow from start to end.
The purpose of End to end testing is to simulate the real user scenario and validate the system under
test and its components for integration and data integrity.
https://www.softwaretestinghelp.com/what-is-end-to-end-testing/
Source: softwaretestinghelp.com
Sample Scenario
1. Register
2. Login into System
3. Add Product to Wishlist
4. Add Product to Wishlist
5. Check the Wishlist
6. Remove a Product from Wishlist
7. Add a Product from Wishlist To Shopping Cart
8. Delete a Product From Shopping Cart
9. Verifying the state
What is Recording HTTP Proxy?
A tool that records and replays back raw http requests. The request is sent to the proxy,
recorded in a data store, and the response from the server is returned back to the client.
Client Server
HTTP Recording
Proxy
Data Store
Recording Process
Client Server
HTTP Recording
Proxy
Data Store
Playback Process
Client Server
HTTP Recording
Proxy
Data Store
Replay Process
Why using it?
● Keep The Whole HTTP Stack, Serialization, Events, etc.
● Everything is happening only on a server level. No GUI required
● Can replay at anytime
● Can Simulate More Than One Client
● Independent from FE
● Easy to Record.
Disadvantages
● Flaky/Fragile Tests
● Less Debug Info
● Costly in terms of speed
My Concept
Discovering the unexpected is more important than
confirming the known.
George E. P. Box
“
“
● We should be able discover how our project works
● Once recorded, a test should work anytime if no code changed.
● Supports Versions
● Easy To Record
My Goals
Data Store
Why Apache Solr?
1. I know how to use it
2. Easy to Search
3. Structureless
4. Easy Integration With PHP. Good HTTP API, solariumphp, SolPHP (official)
Recordings Structure
.HAR Files Format
.HAR Files Format
“The HTTP Archive format, or HAR, is a JSON-formatted archive file format for
logging of a web browser's interaction with a site. The common extension for these
files is .har.” Wikipedia.org
Why HAR Format?
● Not Created For Recordings!
● Purpose: Debugging HTTP Requests.
● All Info in one place.
● Supported By Other Tools: Postman, Browsers
Considerations
● CSRF protection
● Authentication
● Database
● Code Coverage
PART II
Third Party Integration
WireMock
About WireMock
● WireMock was originally created by Tom Akehurst in 2011
● Often described as a HTTP Server Mocking Tool
● Apache 2.0 License
● Java
● Good PHP Support via PHP Library
● Good HTTP API
Website Web Service
http://localhost:8080/
Website Web Service
/files /mappings
http://localhost:8080/
$ java -jar wiremock-standalone-2.25.1.jar
port: 8080
enable-browser-proxying: false
disable-banner: false
no-request-journal: false
verbose: false
$ composer require --dev wiremock-php/wiremock-php
Mappings
GET - /best_conf - Should Return “BGPHP”
Mappings
{
"request": {
"method": "GET",
"urlPattern": "/conferences/best_in_the_world"
},
"response": {
"body" : "<items><item><name>BGPHP</name></item></items>"
}
}
Types Of Mappings?
● File-Based
● In-Memory
Recording File-Based Mappings
$ java -jar wiremock-standalone-2.25.1.jar --record-mappings --proxy-all
https://google.com
{
"id" : "6f612257-1241-339a-a558-7707e8d8c9bc",
"request" : {
"url" : "/",
"method" : "GET"
},
"response" : {
"status" : 200,
"bodyFileName" : "body-YE3ab.txt",
"headers" : {
"Content-Type" : "text/html; charset=UTF-8",
"Date" : "Fri, 01 Nov 2019 17:10:09 GMT",
"Expires" : "Fri, 01 Nov 2019 17:10:09 GMT",
"Cache-Control" : "private, max-age=2592000",
"Server" : "gws",
}
},
"uuid" : "6f612257-1241-339a-a558-7707e8d8c9bc"
}
Stubbing
Creating a mapping programmatically
Using PHP
// Create an object to administer a WireMock instance. This is assumed to be at
// localhost:8080 unless these values are overridden.
$wireMock = WireMock::create();
// Stub out a request
$wireMock->stubFor(WireMock::get(WireMock::urlEqualTo('/some/url'))
->willReturn(WireMock::aResponse()
->withHeader('Content-Type', 'text/plain')
->withBody('Hello world!')));
// ... interact with the server ...
// Verify a request
$wireMock->verify(WireMock::postRequestedFor(WireMock::urlEqualTo('/verify/this'))
->withHeader('Content-Type', WireMock::equalTo('text/xml')));
// Create an object to administer a WireMock instance. This is assumed to be at
// localhost:8080 unless these values are overridden.
$wireMock = WireMock::create();
// Stub out a request
$wireMock->stubFor(WireMock::get(WireMock::urlEqualTo('/some/url'))
->willReturn(WireMock::aResponse()
->withHeader('Content-Type', 'text/plain')
->withBody('Hello world!')));
// ... interact with the server ...
// Verify a request
$wireMock->verify(WireMock::postRequestedFor(WireMock::urlEqualTo('/verify/this'))
->withHeader('Content-Type', WireMock::equalTo('text/xml')));
{
"id" : "6f612257-1241-339a-a558-7707e8d8c9bc",
"request" : {
"url" : "/some/url",
"method" : "GET"
},
"response" : {
"body" : "Hello World",
"headers" : {
"Content-Type" : "text/plain"
}
},
"uuid" : "6f612257-1241-339a-a558-7707e8d8c9bc"
}
// Verify a request
$wireMock->verify(WireMock::postRequestedFor(WireMock::urlEqualTo('/verify/this'))
->withHeader('Content-Type', WireMock::equalTo('text/xml')));
View Mappings
http://localhost:8080/__admin/mappings
Recorder
http://localhost:8080/__admin/recorder/
$ curl http://localhost:8080/recordables/123
{
"message": "Congratulations ...."
}
POST /__admin/recordings/start
{
"targetBaseUrl": "http://example.mocklab.io"
}
POST /__admin/recordings/stop
Snapshotting
Snapshotting is effectively “recording after
the fact”. Rather than starting recording at a
specific point, snapshotting allows you to
convert requests already received by
WireMock into stub mappings.
POST /__admin/recordings/snapshot
{}
Customising Your Recordings
POST /__admin/recordings/start
{
"targetBaseUrl" : "http://example.mocklab.io",
"filters" : {
"urlPathPattern" : "/api/.*",
"method" : "GET",
},
"captureHeaders" : {
"Accept" : { },
"Content-Type" : {
"caseInsensitive" : true
}
},
"requestBodyPattern" : {
"matcher" : "equalToJson",
"ignoreArrayOrder" : false,
"ignoreExtraElements" : true
},
"extractBodyCriteria" : {
"textSizeThreshold" : "2048",
"binarySizeThreshold" : "10240"
},
}
States
Example: To Do List
Started
1 Item
State 2
State 2
2 Items
State 3
State 3
3 Items
Started
GET /todo/items GET /todo/items GET /todo/items
Started State
{
"scenarioName": "To do list",
"requiredScenarioState": "Started",
"newScenarioState": "State 2",
"request": {
"method": "GET",
"url": "/todo/items"
},
"response": {
"status": 200,
"body" : "<items><item>Buy milk</item></items>"
}
}
State 2
{
"scenarioName": "To do list",
"requiredScenarioState": "State 2",
"newScenarioState": "State 3",
"request": {
"method": "GET",
"url": "/todo/items"
},
"response": {
"status": 200,
"body" : "<items><item>Buy milk</item><item>Cancel newspaper
subscription</item></items>"
}
}
State 3
{
"scenarioName": "To do list",
"requiredScenarioState": "State 3",
"newScenarioState": "Started",
"request": {
"method": "GET",
"url": "/todo/items"
},
"response": {
"status": 200,
"body" : "<items><item>Buy milk</item><item>Cancel newspaper
subscription</item><item>Attend BGPHP Conference</item></items>"
}
}
Wiremock browser extension
More About WireMock
● Advanced HTTP Mocking With WireMock - Sam Edwards - droidcon 2017
● http://wiremock.org/external-resources/
Part III
Opencart Test Store
What we have to deal with?
Goal: Integrate, Test and Measure Results
Goal: Integrate, Test and Measure Results
Goal: Integrate, Test and Measure Results
Goal: Integrate, Test and Measure Results
composer.json
{
"require-dev": {
"phpunit/php-code-coverage": "^6.1",
"phpunit/phpcov": "^5.0"
}
}
composer.json
{
"require-dev": {
"phpunit/php-code-coverage": "^6.1",
"phpunit/phpcov": "^5.0"
}
}
composer.json
{
"require-dev": {
"phpunit/php-code-coverage": "^6.1",
"phpunit/phpcov": "^5.0"
}
}
require_once "vendor/autoload.php";
$headers = getallheaders();
require_once "vendor/autoload.php";
$headers = getallheaders();
...
if (isset($headers['http-proxy-request-id'])) {
$coverage = new CodeCoverage();
$coverage->filter()->addDirectoryToWhitelist('catalog/');
$coverage->filter()->addDirectoryToWhitelist('system/');
$coverage->start($headers['http-proxy-request-id']);
}
...
if (xdebug_code_coverage_started()) {
$coverage->stop();
$writer = new SebastianBergmannCodeCoverageReportPHP;
$writer->process($coverage, 'coverage-report/php-' . $headers['http-proxy-request-id'] . '.cov');
}
require_once "vendor/autoload.php";
$headers = getallheaders();
...
if (isset($headers['http-proxy-request-id'])) {
$coverage = new CodeCoverage();
$coverage->filter()->addDirectoryToWhitelist('catalog/');
$coverage->filter()->addDirectoryToWhitelist('system/');
$coverage->start($headers['http-proxy-request-id']);
}
...
if (xdebug_code_coverage_started()) {
$coverage->stop();
$writer = new SebastianBergmannCodeCoverageReportPHP;
$writer->process($coverage, 'coverage-report/php-' . $headers['http-proxy-request-id'] . '.cov');
}
require_once "vendor/autoload.php";
$headers = getallheaders();
...
if (isset($headers['http-proxy-request-id'])) {
$coverage = new CodeCoverage();
$coverage->filter()->addDirectoryToWhitelist('catalog/');
$coverage->filter()->addDirectoryToWhitelist('system/');
$coverage->start($headers['http-proxy-request-id']);
}
...
if (xdebug_code_coverage_started()) {
$coverage->stop();
$writer = new SebastianBergmannCodeCoverageReportPHP;
$writer->process($coverage, 'coverage-report/php-' . $headers['http-proxy-request-id'] . '.cov');
}
require_once "vendor/autoload.php";
$headers = getallheaders();
...
if (isset($headers['http-proxy-request-id'])) {
$coverage = new CodeCoverage();
$coverage->filter()->addDirectoryToWhitelist('catalog/');
$coverage->filter()->addDirectoryToWhitelist('system/');
$coverage->start($headers['http-proxy-request-id']);
}
...
if (xdebug_code_coverage_started()) {
$coverage->stop();
$writer = new SebastianBergmannCodeCoverageReportPHP;
$writer->process($coverage, 'coverage-report/php-' . $headers['http-proxy-request-id'] . '.cov');
}
After a Couple of Clicks:
$ vendor/bin/phpcov merge [files location] --html [report location]
$ vendor/bin/phpcov merge [files location] --html [report location]
phpcov 2.0.0 by Sebastian Bergmann.
Generating code coverage report in HTML format ... done
Conclusion
He who thinks a tool can solve all problems, has a
new problem.
Frederico Toledo
“
“
It’s not a perfect solution
Don’t Stop Writing Tests
Different Approach
Measure The Results
HTTP Recording Proxy Tools
● JMeter - Apache
● AQA HTTP Recorder - Chrome
● IBM Rational Tester Workbench
● PoliJS - Netflix
● Postman
Thank you.

The Recording HTTP Proxy: Not Yet Another Messiah - Bulgaria PHP 2019