Skip to content

Commit d415598

Browse files
committed
first release
1 parent 7c5aa7e commit d415598

File tree

12 files changed

+502
-0
lines changed

12 files changed

+502
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/vendor/
2+
/bin/moco
3+
/composer.lock

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Moco Extension for Behat 3
2+
3+
[Moco](https://github.com/dreamhead/moco) is a stub server we used to use in test env.
4+
5+
Here is a small extension to make its usage more friendly.
6+
7+
## Usage
8+
9+
```yml
10+
default:
11+
extensions:
12+
Rezzza\MocoBehatExtension\MocoExtension:
13+
json_file: features/fixtures.yml
14+
suites:
15+
default:
16+
contexts:
17+
- Rezzza\MocoBehatExtension\MocoContext:
18+
mocoIp: 127.0.0.1
19+
mocoPort: 9997
20+
```
21+
22+
Then you just need to add `MocoWriter` as an argument of your context.
23+
24+
[See tests](features) for more details

bin/install-moco.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
curl -sS https://raw.githubusercontent.com/dreamhead/moco/master/moco-shell/moco > bin/moco \
2+
&& chmod 0755 bin/moco \
3+
&& bin/moco version

composer.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "rezzza/moco-behat-extension",
3+
"description": "Helper to use moco with behat",
4+
"type": "behat-extension",
5+
"license": "MIT",
6+
"homepage": "https://github.com/rezzza/moco-behat-extension",
7+
"authors": [
8+
{
9+
"name": "RezZza Team",
10+
"email": "[email protected]"
11+
}
12+
],
13+
"autoload": {
14+
"psr-4": { "Rezzza\\MocoBehatExtension\\": "src/" }
15+
},
16+
"require": {
17+
"php": ">=5.5.9",
18+
"behat/behat": "~3.0"
19+
},
20+
"require-dev": {
21+
"rezzza/rest-api-behat-extension": "^3.1",
22+
"symfony/process": "^3.0"
23+
}
24+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<?php
2+
3+
use mageekguy\atoum\asserter;
4+
use Behat\Behat\Context\Context;
5+
use Behat\Gherkin\Node\PyStringNode;
6+
use Symfony\Component\Process\Process;
7+
use Symfony\Component\Process\PhpExecutableFinder;
8+
9+
/**
10+
* Test workflow totally copied from https://github.com/Behat/WebApiExtension/blob/master/features/bootstrap/FeatureContext.php
11+
*/
12+
class FeatureContext implements Context
13+
{
14+
private $phpBin;
15+
16+
private $process;
17+
18+
private $workingDir;
19+
20+
private $asserter;
21+
22+
private $moco;
23+
24+
public function __construct()
25+
{
26+
$this->asserter = new asserter\generator();
27+
}
28+
29+
/**
30+
* @BeforeSuite
31+
* @AfterSuite
32+
*/
33+
public static function cleanTestFolders()
34+
{
35+
$dir = self::workingDir();
36+
37+
if (is_dir($dir)) {
38+
self::clearDirectory($dir);
39+
}
40+
}
41+
42+
/**
43+
* @BeforeScenario
44+
*/
45+
public function prepareScenario()
46+
{
47+
$dir = self::workingDir() . DIRECTORY_SEPARATOR . md5(microtime() * rand(0, 10000));
48+
mkdir($dir . '/features/bootstrap', 0777, true);
49+
50+
$phpFinder = new PhpExecutableFinder();
51+
52+
if (false === $php = $phpFinder->find()) {
53+
throw new \RuntimeException('Unable to find the PHP executable.');
54+
}
55+
56+
$this->workingDir = $dir;
57+
$this->phpBin = $php;
58+
$this->process = new Process(null);
59+
}
60+
61+
/**
62+
* @Given /^a file named "(?P<filename>[^"]*)" with:$/
63+
*/
64+
public function aFileNamedWith($filename, PyStringNode $fileContent)
65+
{
66+
$content = strtr((string) $fileContent, array("'''" => '"""'));
67+
$this->createFile($this->workingDir . '/' . $filename, $content);
68+
}
69+
70+
/**
71+
* @Given I start moco
72+
*/
73+
public function iStartMoco()
74+
{
75+
$fixturesFile = sprintf('%s/features/fixtures.yml', $this->workingDir);
76+
file_put_contents($fixturesFile, '[]');
77+
$this->moco = new Process(sprintf('bin/moco start -p 9999 -c %s', $fixturesFile));
78+
$this->moco->start();
79+
sleep(2); // Let moco start
80+
}
81+
82+
/**
83+
* @When /^I run behat "(?P<arguments>[^"]*)"$/
84+
*/
85+
public function iRunBehat($arguments)
86+
{
87+
$argumentsString = strtr($arguments, array('\'' => '"'));
88+
$this->process->setWorkingDirectory($this->workingDir);
89+
$this->process->setCommandLine(sprintf(
90+
'%s %s %s %s',
91+
$this->phpBin,
92+
escapeshellarg(BEHAT_BIN_PATH),
93+
$argumentsString,
94+
strtr('--no-colors', array('\'' => '"', '"' => '\"'))
95+
));
96+
$this->process->start();
97+
$this->process->wait();
98+
}
99+
100+
/**
101+
* @Then /^it should (fail|pass) with:$/
102+
*/
103+
public function itShouldTerminateWithStatusAndContent($exitStatus, PyStringNode $string)
104+
{
105+
// echo sprintf('%s/features/fixtures.yml', $this->workingDir);
106+
107+
// var_dump($this->moco->getOutput());
108+
// var_dump($this->moco->getErrorOutput());
109+
// var_dump($this->getOutput());exit;
110+
if ('fail' === $exitStatus) {
111+
$this->asserter->variable($this->getExitCode())->isEqualTo(1);
112+
} elseif ('pass' === $exitStatus) {
113+
$this->asserter->variable($this->getExitCode())->isEqualTo(0);
114+
} else {
115+
throw new \LogicException('Accepts only "fail" or "pass"');
116+
}
117+
$this->asserter->string($this->getOutput())->contains((string) $string);
118+
}
119+
120+
private function getExitCode()
121+
{
122+
return $this->process->getExitCode();
123+
}
124+
125+
private function getOutput()
126+
{
127+
$output = $this->process->getErrorOutput() . $this->process->getOutput();
128+
129+
// Normalize the line endings in the output
130+
if ("\n" !== PHP_EOL) {
131+
$output = str_replace(PHP_EOL, "\n", $output);
132+
}
133+
134+
return trim(preg_replace("/ +$/m", '', $output));
135+
}
136+
137+
private function createFile($filename, $content)
138+
{
139+
$path = dirname($filename);
140+
141+
if (!is_dir($path)) {
142+
mkdir($path, 0777, true);
143+
}
144+
145+
file_put_contents($filename, $content);
146+
}
147+
148+
public static function workingDir()
149+
{
150+
return sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'json-api-behat';
151+
}
152+
153+
private static function clearDirectory($path)
154+
{
155+
$files = scandir($path);
156+
array_shift($files);
157+
array_shift($files);
158+
159+
foreach ($files as $file) {
160+
$file = $path . DIRECTORY_SEPARATOR . $file;
161+
if (is_dir($file)) {
162+
self::clearDirectory($file);
163+
} else {
164+
unlink($file);
165+
}
166+
}
167+
168+
rmdir($path);
169+
}
170+
}

features/check-moco.feature

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Feature: Check moco
2+
In order to not lost time to understand why a test fail
3+
As a developer
4+
I should be able to know when moco is not running
5+
6+
Scenario: Moco is not started
7+
Given a file named "behat.yml" with:
8+
"""
9+
default:
10+
extensions:
11+
Rezzza\MocoBehatExtension\MocoExtension:
12+
json_file: features/fixtures.yml
13+
Rezzza\RestApiBehatExtension\Extension:
14+
rest:
15+
base_url: http://127.0.0.1:9997
16+
adaptor_name: curl
17+
suites:
18+
default:
19+
contexts:
20+
- Rezzza\RestApiBehatExtension\RestApiContext:
21+
- Rezzza\MocoBehatExtension\MocoContext:
22+
mocoIp: 127.0.0.1
23+
mocoPort: 9997
24+
"""
25+
And a file named "features/call_moco.feature" with:
26+
"""
27+
Feature: Call Moco
28+
In order to mock external call
29+
As a feature runner
30+
I need to call moco
31+
32+
@moco
33+
Scenario: Call moco
34+
When I send a GET request to "/coucou"
35+
"""
36+
When I run behat "-f progress features/call_moco.feature"
37+
Then it should fail with:
38+
"""
39+
You should run moco by "bin/moco start -p 9997 -c features/fixtures.yml"
40+
"""

features/mock-http-call.feature

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
Feature: Mock HTTP call
2+
In order to test how my system behaves with third party services
3+
As a developer
4+
I should be able to mock external HTTP call
5+
6+
Scenario: Mock external call
7+
Given a file named "behat.yml" with:
8+
"""
9+
default:
10+
extensions:
11+
Rezzza\MocoBehatExtension\MocoExtension:
12+
json_file: features/fixtures.yml
13+
Rezzza\RestApiBehatExtension\Extension:
14+
rest:
15+
base_url: http://127.0.0.1:9999
16+
adaptor_name: curl
17+
suites:
18+
default:
19+
contexts:
20+
- FeatureContext
21+
- Rezzza\RestApiBehatExtension\RestApiContext:
22+
- Rezzza\MocoBehatExtension\MocoContext:
23+
mocoIp: 127.0.0.1
24+
mocoPort: 9999
25+
"""
26+
And a file named "features/bootstrap/FeatureContext.php" with:
27+
"""
28+
<?php
29+
use Behat\Behat\Context\Context;
30+
use Rezzza\MocoBehatExtension\MocoWriter;
31+
32+
class FeatureContext implements Context
33+
{
34+
private $mocoWriter;
35+
36+
public function __construct(MocoWriter $mocoWriter)
37+
{
38+
$this->mocoWriter = $mocoWriter;
39+
}
40+
41+
/**
42+
* @Given the url :requestUrl returns :responseContent
43+
*/
44+
public function mockHttpCall($requestUrl, $responseContent)
45+
{
46+
$this->mocoWriter->mockHttpCall(
47+
['uri' => $requestUrl],
48+
['status' => 200, 'text' => $responseContent]
49+
);
50+
$this->mocoWriter->writeForMoco();
51+
}
52+
}
53+
"""
54+
And a file named "features/call_moco.feature" with:
55+
"""
56+
Feature: Call Moco
57+
In order to mock external call
58+
As a feature runner
59+
I need to call moco
60+
61+
@moco
62+
Scenario: Call moco
63+
Given the url "/coucou/hibou" returns "Hey you, this is crazy"
64+
When I send a GET request to "/coucou/hibou"
65+
Then print response
66+
"""
67+
And I start moco
68+
When I run behat "features/call_moco.feature"
69+
Then it should pass with:
70+
"""
71+
Hey you, this is crazy
72+
"""

src/MocoContext.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Rezzza\MocoBehatExtension;
4+
5+
use Behat\Behat\Context\Context;
6+
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
7+
8+
class MocoContext implements Context
9+
{
10+
private $mocoWriter;
11+
12+
private $mocoIp;
13+
14+
private $mocoPort;
15+
16+
public function __construct(MocoWriter $mocoWriter, $mocoIp, $mocoPort)
17+
{
18+
$this->mocoWriter = $mocoWriter;
19+
$this->mocoIp = $mocoIp;
20+
$this->mocoPort = $mocoPort;
21+
}
22+
23+
/**
24+
* @BeforeScenario @moco
25+
*/
26+
public function before(BeforeScenarioScope $scope)
27+
{
28+
if (false === @fsockopen($this->mocoIp, $this->mocoPort, $errno, $errstr, 3)) {
29+
throw new \Exception(
30+
sprintf('You should run moco by "bin/moco start -p %s -c %s"', $this->mocoPort, $this->mocoWriter->getJsonFile())
31+
);
32+
}
33+
$this->mocoWriter->reset();
34+
}
35+
}

0 commit comments

Comments
 (0)