Mocking a user shops response with Codeception modules while developing Etsy Little Helper.
The problem
I need to test the code parsing Etsy API responses in a variety of situations and before a single line of that code is written I need a mock server in place.
My luck is in the possibility Codeception offers to create modules to extend its functionalities and I’ve created the first method I need to simulate different responses when requesting for a user shops.
Template
To properly simulate the response I’ve transformed the one I’ve posted on my earlier article to be used by Smarty; a template engine is my go-to solution in this cases and Smarty allows for quite some flexibility.
Without too much complication I’ve transformed the JSON response to default all of its values but the one I will surely need: a list of shop_id
s to populate it.
{
"count": {$shopIds|@count},
"results": [
{foreach $shopIds as $shopId}
{
"shop_id": {$shopId},
"shop_name": "{$shopName|default:'MyEtsyShop'}",
"user_id": {$userId|default:222222},
"creation_tsz": 1393407522,
"title": "{$title|default:'My etsy shop'}",
"announcement": "{$announcement|default:'Crypto-tattoo tank-traps market table convenience store render-farm.'}",
"currency_code": "{$currencyCode|default:'$'}",
"is_vacation": {$isVacation|default:'false'},
"vacation_message": "{$vacationMessage|default:'Boat silent shanty town wristwatch ablative car nodality Tokyo. Concrete bridge hacker human youtube rebar RAF computer boy savant face forwards. Corrupted human claymore mine Shibuya monofilament tube city 8-bit futurity woman dolphin.'}",
"sale_message": "{$saleMessage|default:'Tube knife convenience store engine corporation free-market crypto-Chiba tanto computer nano-wonton soup sunglasses. Military-grade office otaku urban realism assault uplink grenade sprawl BASE jump motion man Chiba render-farm. Shibuya into systema dead vehicle modem courier car vinyl pen alcohol San Francisco sprawl disposable woman. Nodal point girl RAF papier-mache tower rain smart-augmented reality order-flow dolphin otaku vinyl artisanal. Post-shoes futurity grenade euro-pop pistol artisanal kanji alcohol DIY tank-traps math-concrete Chiba.'}",
"digital_sale_message": "{$digitalSaleMessage|default:'Sub-orbital grenade realism man post-face forwards garage table pistol shrine. Tower paranoid free-market soul-delay BASE jump range-rover A.I. RAF nodality corporation j-pop katana advert ablative rain car sign. Legba BASE jump assassin savant artisanal sign nodal point digital nodality faded meta-j-pop RAF computer engine convenience store systema.'}",
"last_updated_tsz": 1427289394,
"listing_active_count": {$listingActiveCount|default:23},
"login_name": "{$loginName|default:'johndoe'}",
"accepts_custom_requests": {$acceptsCustomRequests|default:'false'},
"policy_welcome": "{$policyWelcome|default:'Math-BASE jump receding soul-delay tanto shrine Shibuya. Market hacker sunglasses film face forwards 8-bit table kanji assassin katana construct augmented reality monofilament. Shoes sprawl skyscraper knife network youtube man Shibuya city cartel cardboard. Free-market shrine towards 3D-printed euro-pop nodal point sub-orbital monofilament convenience store singularity motion car Kowloon augmented reality. A.I. hacker market free-market crypto-lights urban franchise fetishism cyber-hotdog neon film youtube.'}",
"policy_shipping": "{$policyShipping|default:'Bridge 8-bit soul-delay disposable dolphin convenience store fetishism shoes towards sunglasses girl camera saturation point vehicle courier.'}",
"policy_refunds": "{$policyRefund|default:'Nodality singularity shoes wristwatch savant ablative marketing 3D-printed convenience store corporation woman car artisanal RAF free-market physical.'}",
"policy_additional": {$policyAdditional|default:'null'},
"policy_seller_info": {$policySellerInfo|default:'null'},
"policy_updated_tsz": 1396363691,
"vacation_autoreply": {$vacationAutoreply|default:'null'},
"url": "https:\/\/www.etsy.com\/shop\/{$shopName|default:'MyEtsyShop'}?utm_source=etsylittlehelper&utm_medium=api&utm_campaign=api",
"image_url_760x100": "https:\/\/img0.etsystatic.com\/033\/0\/{$shopId}\/iusb_760x100.13409064_z8j1.jpg",
"num_favorers": {$numFavorers|default:200},
"languages": ["en-US"],
"upcoming_local_event_id": null
}
{if $shopIds|@count gt 1},{/if}
{/foreach}
],
"params": {
"user_id": "{$loginName|default:'johndoe'}",
"limit": 25,
"offset": 0,
"page": null
},
"type": "Shop",
"pagination": {
"effective_limit": 25,
"effective_offset": 0,
"next_offset": null,
"effective_page": 1,
"next_page": null
}
}
That template will be fed to an helper class, a Codeception module, I’ve called EtsyApi
(file is /tests/_support/EtsyApi.php
)
namespace Helper;
use Codeception\Module;
class EtsyApi extends Module {
/**
* @var Smarty
*/
private $smarty;
public function getUserShopsResponse( array $shopIds, array $customData = [ ] ) {
$smarty = $this->getSmarty();
$smarty->assign( 'shopIds', $shopIds );
foreach ( $customData as $key => $value ) {
$smarty->assign( $key, $value );
}
return $smarty->fetch( $this->getShopTemplate() );
}
private function getShopTemplate() {
return dirname( __FILE__ ) . '/templates/shopResponse.tpl';
}
private function getSmarty() {
if ( empty( $this->smarty ) ) {
$this->smarty = new \Smarty();
}
return $this->smarty;
}
}
I just need to add that to the modules my UnitTester
class should use (file is tests/unit_suite.yml
)
# Codeception Test Suite Configuration
# suite for unit (internal) tests.
class_name: UnitTester
modules:
enabled: [Asserts, \Helper\Unit, \Helper\EtsyApi]
run the command that will tell Codeception to rebuild the helpers
codeception build
and create a first test for a still not-existing class that will use that method
codecept generate:test ELH_UserShopsParser
in that I can finally run a test to prove that the response mocker works
class ELH_ShopsParserTest extends \Codeception\TestCase\Test {
/**
* @var \UnitTester
*/
protected $tester;
protected function _before() {
}
protected function _after() {
}
// tests
public function testMockUserShopResponse() {
$shop_id = '1111111';
$userShopsResponse = $this->tester->getUserShopsResponse( [ $shop_id ] );
$response = json_decode( $userShopsResponse );
$this->assertTrue( $response->results[0]->shop_id == $shop_id );
}
}