Wsunit
An awful lot of my daily worktasks are talking to different webservices and/or external APIs over Http. To ensure the quality and functionality of my implementations I switched to test driven development quite some time ago. As you might know one differs tests (or better verifications) in those who verify the smallest runnable unit (unit test) and those who verify the integration of each system under test (SUT). This diversion often forces you to write similar tests for each of the approaches. Which is in most cases copying the unit test to your integration test suite and replace the fixture file by the real request. Getting bored by writing test all over again, I thought of a different and at least for me better approach. So I came up with wsunit.
Testing webservices
The Main purpose of wsunit is to get rid of the boring repetitions or dangerous copy paste testing. Imageine you write your unit tests to verify the behavior of your implementation using the response of a web service in that way that it is using a fixture file. The fixture file itself at least contains the response body and in more advanced cases also the resonpse header. When writing this unit test at least for me the usual behavior is not to rely on a probably provided documentation of the service but sending a request to the service and record it's response. I always make sure I save the header for further processing. The response data (header and body) are stored in a computer readable format (XML) in the fixture file for further processing. Your test now gets the data from the fixture file and verifies whatever implementation you want to be sure of. All good. Now the servide provider decides to alter the response without notifying you. Using a fixture file recorded some time ago you are not able to detect this change by running you test suite. Worse case your credit card payment process gets messed up and the customer will not get charged for his shopping. Somewhen you get somehow notified that there is something wrong with a service. You decide to fix this right away and even write an integration test for it. If you are not as lazy as I am you write a new test sending a real request to the service, receiving it's responce, and using this one for further processing. Then you realize that there are a number of unit tests relying on the correct behavior of the external service. Sure you now start to implement test for each and every possible response of the service to really be sure it behaves as agreed on.
Wsunit to the rescue
This is the point where wsunit plays it's game. Once setup it sends requests to configured locations, fetches the response and saves it for further processing. The benefit for you is that you just have to write unit tests as you already did in the first place. The rest is done by configuration which test shall call which location.
Setup and configuration
First clone wsunit from github or fork it and clone it from your own repository. The configuration has two parts. One is the registration of the actual test listener to PHPUnit, the 2nd is the definition of which test (identified by it's name) shall be request the response of which location.
Since your tests will wait for the service to answer they will run a lot slower as your unit tests. It is a common best practice to separate those integration tests into a separate job on your CI-server to prevent the developer to run those tests while developing.
PHPUnit
For the PHPUnit configuration please visit http://www.phpunit.de/manual/current/en/appendixes.configuration.html and read the 'Test Listener' section or copy the example to your configuration and simply adapt the location of your configuration file.
<listeners>
<listener class="WebServiceListener">
<arguments>
<object
class="Extensions_Webservice_Listener_Factory"
/>
<object
class="Extensions_Webservice_Listener_Loader_Configuration"
/>
<array>
<element key="httpClient">
<string>
Extensions_Webservice_Listener_Http_Client
</string>
</element>
<element key="logger">
<string>
Extensions_Webservice_Listener_Logger
</string>
</element>
<element key="configuration">
<string>
/path/to/configuration.xml
</string>
</element>
</array>
</arguments>
</listener>
</listeners>
Arguments
- (object) Extensions_Webservice_Listener_Factory
- Factory class providing objects mandatory for the operation of the listener.
- (object) Extensions_Webservice_Listener_Loader_Configuration
- Object to load the configuration file.
- (array) Contains the names of classes to be registered to the factory and
- the location of the location definition file.
Test listener configuration
Beside making PHPUnit aware of the test listener and to actually make each test aware of the loaction the response shall be fetched from, a 2nd configuration file is needed. The following example show such a configuration.
The name and the location of the configuration file is set in the element[key='configuration'] element of the test listener registration in PHPUnit.
<?xml version="1.0" encoding="UTF-8"?>
<listener>
<serializer>
Extensions_Webservice_Serializer_Http_Response
</serializer>
<test case="Example_TestCase" name="testGetData">
<location href="http://example.org/data.txt" />
</test>
<test
case="Extensions_Webservice_Constraint_JsonErrorMessageProviderTest"
name='testTranslateTypeToPrefix with data set "expected"'
>
<serializer>
Extensions_Webservice_Serializer_Http_Response
</serializer>
<location
dataName="expected"
href="http://blog.bastian-feder.de/blog.rss"
>
<query>
<param name="mascott[]">tux</param>
<param name="mascott[RedHat]">beastie</param>
<param name="os">Linux</param>
</query>
</location>
</test>
</listener>
Available tags
test: Encapsulated with the 'listener' tag as root tag each test to be recognized is represented by a 'test' tag and section. A test is recognized by the test case (case attribute) and name (name attribute) of a test. If your test registers a dataprovider be aware that the name of the test will be altered by PHPUnit (see the second test section in the example).
serializer: This tag can be defined in the <listener> tag to be to overall used serialize or in the <test> configuration to override a globally set serializer.
location: Defined within the test section it defines the location and it's optional query string. Once these information are set the configured location will be tackled for it's response.
Beyond setup and configuration
Once you finished the setup and configuration you just call PHPunit as usual and wsunit will handle the fixture files for the configured tests. If a test will be run again wsunit does not override an existing fixture file due to further investigations. But this leaves you with the task to clean up the directory once in a while.
Happy testing!
Trackbacks
Comments
-
Adrian on Fri, 15 Feb 2013 00:32:05 +0100
Hi Bastian,
Link to comment
I just found out about wsunit and think it\'s a great idea for testing APIs.
I\'m looking for a good way to port VCR (ruby lib) to php, see
http://www.reddit.com/r/PHP/comments/18jag3/how_to_port_vcr_ruby_lib_to_php/
VCR is a tool to automatically record and playback HTTP interactions. So it\'s more about providing fixtures for code which uses APIs.
It looks like you already store and read fixture data so this might come in handy. Do you think it makes sense to extend wsunit or create another project which handles automatic HTTP(s) interception?
Cheers,
Adrian -
Bastian on Sat, 16 Feb 2013 19:46:36 +0100
Hi Adrian,
Link to comment
igrow is right. It seem WSUnit does exactly what you want to do. Cuttently it is not in a stable state - I was way too busy the last months to get there - but I use it in many projects of my professional work.
If you ask me about my oppinion abut to extend WSUnit I say go for it. Next thing I am planning is the introduction to intercept SOAP requests sinc there is an actual need for me to have it ;)
If you are interested in joining forces to bring WSUnit forward don\'t heasitate contacting me via mail lapisATbastian-feder.de.
Looking forward to hearing from you.
Bastian
Fields with bold names are mandatory.
Wsuint - the first release on Fri, 04 May 2012 11:48:04 +0200 in Liip Blog
It is always the same issue... you write unit tests for a web-service, making you feel confident about the correctness of your code and then the service provider changed the response without notifying ...