What are the Slack Archives?
It’s a history of our time together in the Slack Community! There’s a ton of knowledge in here, so feel free to search through the archives for a possible answer to your question.
Because this space is not active, you won’t be able to create a new post or comment here. If you have a question or want to start a discussion about something, head over to our categories and pick one to post in! You can always refer back to a post from Slack Archives if needed; just copy the link to use it as a reference..
Hello everybody, does anybody has experience with faking/mocking external dependencies (like externa
Comments
-
i think @UKGT7RC7P mentioned php vcr in one of his past talks
0 -
The question is more, how they get integrated in spryker, so where is the switch to decide between mock call/real call and so on … does this also cover the topic?
0 -
We have build a module for that which determines which implementation should be used for a given interface.
interface MerchantServicePluginInterface { /** * @param \Generated\Shared\Transfer\MerchantServiceQueryTransfer $query * * @return bool */ public function isMatching(MerchantServiceQueryTransfer $query): bool; /** * @return int */ public function getPriority(): int; } class ServiceDefinitionMatcher { /** * @var \Pyz\Zed\MerchantServices\Dependency\MerchantServicePluginInterface[] */ protected $merchantServices; /** * @param \Pyz\Zed\MerchantServices\Dependency\MerchantServicePluginInterface[] $merchantServicePlugins */ public function __construct(array $merchantServicePlugins) { foreach ($merchantServicePlugins as $merchantServicePlugin) { $this->addMerchantServicePlugin($merchantServicePlugin); } } /** * @param \Pyz\Zed\MerchantServices\Dependency\MerchantServicePluginInterface $merchantServicePlugin * * @return void */ public function addMerchantServicePlugin(MerchantServicePluginInterface $merchantServicePlugin) { $this->merchantServices[get_class($merchantServicePlugin)] = $merchantServicePlugin; } /** * @param \Generated\Shared\Transfer\MerchantServiceQueryTransfer $query * * @throws \Pyz\Zed\MerchantServices\Business\Exception\ServiceNotFoundException * * @return string */ public function getServiceClass(MerchantServiceQueryTransfer $query): string { $matchingQueue = new SplPriorityQueue(); foreach ($this->merchantServices as $merchantService) { if ($merchantService->isMatching($query)) { $priority = $merchantService->getPriority(); $matchingQueue->insert($merchantService, $priority); } } if ($matchingQueue->isEmpty() === true) { throw new ServiceNotFoundException(sprintf( 'No service definition found which matches the given query, did you added the plugin to the MerchantServicesDependencyProvider? (Query: %s)', $query->serialize() )); } $priorityMatch = $matchingQueue->top(); return get_class($priorityMatch); }
0 -
Example Mock Implementation:
class MockLiveAvailabilityPlugin extends AbstractPlugin implements LiveAvailabilityPluginInterface, MerchantServicePluginInterface { /** * @param string $sku * @param array $warehouseIds * * @return \Generated\Shared\Transfer\LiveAvailabilitiesCollectionTransfer */ public function getLiveAvailabilityBySku(string $sku, array $warehouseIds = []): LiveAvailabilitiesCollectionTransfer { ... } /** * @param array $skus * @param array $warehouseIds * * @return \Generated\Shared\Transfer\LiveAvailabilitiesCollectionTransfer */ public function getLiveAvailabilitiesBySkus(array $skus, array $warehouseIds = []): LiveAvailabilitiesCollectionTransfer { ... } /** * @param \Generated\Shared\Transfer\MerchantServiceQueryTransfer $query * * @return bool */ public function isMatching(MerchantServiceQueryTransfer $query): bool { return ( $query->getInterface() === LiveAvailabilityPluginInterface::class && $this->getConfig()->isMockActive() === true ); } /** * @return int */ public function getPriority(): int { //high priority, if the mock live availability is active it should be used return 1000; } }
0 -
And where ever the "live service" should be used we use the following code for a facade:
/** * @return \Pyz\Zed\LiveAvailability\Dependency\LiveAvailabilityPluginInterface */ protected function createLiveAvailabilityServiceAdapterPlugin(): LiveAvailabilityPluginInterface { $pluginClass = $this->getMerchantServicesFacade() ->getServiceClassByInterface(LiveAvailabilityPluginInterface::class); $plugin = new $pluginClass(); return $plugin; }
0 -
So it's sort of a prioritized service registry we use were mock services become a very high priority and a config to determine if they are active.
We use the Interface of the Service as registry key, but you could also use a different key.0 -
@UL6DGRULR and determining if a mock is active is actually configured on different project config levels? So there is a config in config_default-docker.php for example which just say, that the mock client is active?
0 -
@UPWG9AYH2 Exactly, so a developer could use his config_local.php to test with different implementations, a test system config could always use the mock and it can be abused to provide different real implementations (we currently use this to toggle if the new ERP or the old ERP API should be used)
0 -
And the prioritization is because you have multiple service that are active … so you would have an order when some of the higher proritized endpoint isnt availabel
0 -
That looks like a clean solution to me … my highest priorities focus on don’t messing up anything in spryker aka introducing ugly code switches etc …
0 -
Yes, the prioritization is the main part here which makes it work that either the mock or the real implementation is returned.
This approach has one obvious issue, if you want to decide very late in the process which implementation should be used, this approach might not be usable.
For example we use a mock implementation if an order will be created and the customer name in the billing address is Tester McTesty, this can't be done with this approach or at least you would need to move the part were the implementation is created (new ${plugin}()
) outside of the factory.0 -
okay, that make sense … so your approach is as far as i see designed for a very general purpose, maybe we dont need it that general in the first run …
However, how does this look like in the dependencyprovider? Is this registered as a new service which gets included like any other dependency in the dependencyprovider of the certain module?0 -
Something like
$container->getLocator()->serviceDefinitionMatcher()
?
0 -
Yes, it generalized as we have this requirement for many services.
If you want to have this only in one place you could adapt the priorization part.And yes, the service definition matcher is a standalone bundle, so it can be found via locator exactly as you have shown.
0 -
Cool, very nice in my opinion. Thanks for your insights 😉
0 -
also 5 cents from my side, you can mock external services/APIs with
donatj/mock-webserver
library.
It starts locally a mock, that listens to the particular port. Then, you can mock any endpoint so that your test would look like that:protected function _before() { $this->server = new MockWebServer(8083, '127.0.0.1'); $this->server->start(); } public function testMyFeature() { $this->server->setResponseOfPath( '/api/some/endpoint, new Response('JSON GERE') ); $facade = new SomeFacade(); $facade->callMethodThatUsesExternalApi() // assertions here } public function _after(): void { $this->server->stop(); }
0 -
_before and _after can be done as a codeception Module/Helper
0 -
the only thing you would need to do - is to set the config for accessing that API, f.e., if you go with a codecept Module approach:
use SprykerTest\Shared\Testify\Helper\ConfigHelper; ... $config = $this->getModule('\\' . ConfigHelper::class); $config->setConfig(ExternalApiConstants::HOST, '127.0.0.1:8083');
0 -
like this you mock “as later as possible” in your process (answering the
where is the switch to decide between mock call/real call and so on
), you don’t need any dependency faking, client mocks and so on0 -
plus, the usage is very simple - and it’s easier to re-use it for your further tests/endpoints, and it’s more TDD friendly if you need
0 -
from all the approaches we’ve tried on one of the projects that was the most elegant so far
0 -
@UKJSE6T47 Thanks a lot, this is a bundle I wasn't aware of and it will help in our project as well 😉
0 -
you’re welcome! Before that we were using another one, but it was node-js based. This one is PHP which was preferable for us
0
Categories
- All Categories
- 42 Getting Started & Guidelines
- 7 Getting Started in the Community
- 8 Additional Resources
- 7 Community Ideas and Feedback
- 76 Spryker News
- 929 Developer Corner
- 787 Spryker Development
- 89 Spryker Dev Environment
- 362 Spryker Releases
- 3 Oryx frontend framework
- 35 Propel ORM
- 68 Community Projects
- 3 Community Ideation Board
- 30 Hackathon
- 3 PHP Bridge
- 6 Gacela Project
- 26 Job Opportunities
- 3.2K 📜 Slack Archives
- 116 Academy
- 5 Business Users
- 370 Docker
- 551 Slack General
- 2K Help
- 75 Knowledge Sharing
- 6 Random Stuff
- 4 Code Testing
- 32 Product & Business Questions
- 70 Spryker Safari Questions
- 50 Random