Skip to content

Commit caa5bb4

Browse files
Too much stuff.
1 parent 00f8cc3 commit caa5bb4

12 files changed

+333
-22
lines changed

LICENSE.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (c) 2015 Martin Helmich <[email protected]>
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.
20+

README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# MongoDB mock classes
2+
3+
## Author and license
4+
5+
Martin Helmich
6+
This library is [MIT-licenced](LICENSE.txt).
7+
8+
## Synopsis and motivation
9+
10+
This class contains an implementation of the `MongoDB/Collection` class that
11+
can store, modify and filter documents in memory, together with a set of
12+
(optional) PHPUnit assertions.
13+
14+
I wrote this library because I wanted to unit-test a library that used MongoDB
15+
collections intensively and felt that mocking the `MongoDB/Collection` class
16+
using PHPUnit's built-in mock builders was too restrictive.
17+
18+
**Note**: Currently, this implementation contains only a subset of the actual
19+
MongoDB collection API. I've only implemented the parts of the API that I needed
20+
for my use case. If you need additional functionality, feel free to open an
21+
issue, or (better yet) a pull request.
22+
23+
## Usage
24+
25+
You can use this class exactly as you'd use the `MongoDB/Collection` class
26+
(in theory, at least -- remember, this package is not API-complete):
27+
28+
```php
29+
use Helmich\MongoMock\MockCollection;
30+
31+
$collection = new MockCollection();
32+
$collection->createIndex(['foo' => 1]);
33+
34+
$documentId = $collection->insertOne(['foo' => 'bar'])->insertedId();
35+
$collection->updateOne(['_id' => $documentId], ['$set' => ['foo' => 'baz']]);
36+
```
37+
38+
## Differences
39+
40+
In some aspects, the `MongoDB\Collection`'s API was extended to allow for better
41+
testability:
42+
43+
1. Filter operands may contain callback functions that are applied to document
44+
properties:
45+
46+
```php
47+
$r = $collection->find([
48+
'someProperty' => function($p) {
49+
return $p == 'bar';
50+
}
51+
]);
52+
```
53+
54+
2. Filter operands may contain PHPUnit constraints (meaning instances of the
55+
`PHPUnit_Framework_Constraint` class). You can easily build these using the
56+
factory functions in the `PHPUnit_Framework_Assert` class.
57+
58+
```php
59+
$r = $collection->find([
60+
'someProperty' => \PHPUnit_Framework_Assert::isInstanceOf(\MongoDB\BSON\Binary::class)
61+
]);
62+
```

src/Assert.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
<?php
22
namespace Helmich\MongoMock;
33

4+
use Helmich\MongoMock\Assert\HasDocumentConstraint;
5+
use Helmich\MongoMock\Assert\IndexWasCreatedConstraint;
46
use Helmich\MongoMock\Assert\QueryWasExecutedConstraint;
7+
use PHPUnit_Framework_Constraint as Constraint;
58

69
class Assert
710
{
8-
public static function executedQuery($filter, $options = [])
11+
public static function collectionExecutedQuery(array $filter, array $options = []): Constraint
912
{
1013
return new QueryWasExecutedConstraint($filter, $options);
1114
}
15+
16+
public static function collectionHasIndex(array $key, array $options = []): Constraint
17+
{
18+
return new IndexWasCreatedConstraint($key, $options);
19+
}
20+
21+
public static function collectionHasDocument(array $filter = []): Constraint
22+
{
23+
return new HasDocumentConstraint($filter);
24+
}
1225
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
namespace Helmich\MongoMock\Assert;
3+
4+
use Helmich\MongoMock\Log\Index;
5+
use Helmich\MongoMock\MockCollection;
6+
7+
class HasDocumentConstraint extends \PHPUnit_Framework_Constraint
8+
{
9+
10+
/**
11+
* @var
12+
*/
13+
private $filter;
14+
15+
public function __construct($filter)
16+
{
17+
parent::__construct();
18+
$this->filter = $filter;
19+
}
20+
21+
protected function matches($other)
22+
{
23+
if (!$other instanceof MockCollection) {
24+
return false;
25+
}
26+
27+
return $other->count($this->filter) > 0;
28+
}
29+
30+
/**
31+
* Returns a string representation of the object.
32+
*
33+
* @return string
34+
*/
35+
public function toString()
36+
{
37+
return 'has document that matches ' . json_encode($this->filter);
38+
}
39+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
namespace Helmich\MongoMock\Assert;
3+
4+
use Helmich\MongoMock\Log\Index;
5+
use Helmich\MongoMock\MockCollection;
6+
7+
class IndexWasCreatedConstraint extends \PHPUnit_Framework_Constraint
8+
{
9+
10+
/**
11+
* @var
12+
*/
13+
private $key;
14+
/**
15+
* @var array
16+
*/
17+
private $options;
18+
19+
public function __construct($key, $options = [])
20+
{
21+
parent::__construct();
22+
$this->key = $key;
23+
$this->options = $options;
24+
}
25+
26+
protected function matches($other)
27+
{
28+
if (!$other instanceof MockCollection) {
29+
return false;
30+
}
31+
32+
$constraint = \PHPUnit_Framework_Assert::equalTo(new Index($this->key, $this->options));
33+
34+
foreach ($other->indices as $index) {
35+
if ($constraint->evaluate($index, '', true)) {
36+
return true;
37+
}
38+
}
39+
40+
return false;
41+
}
42+
43+
/**
44+
* Returns a string representation of the object.
45+
*
46+
* @return string
47+
*/
48+
public function toString()
49+
{
50+
return 'has index of ' . json_encode($this->key);
51+
}
52+
}

src/Assert/QueryWasExecutedConstraint.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?php
22
namespace Helmich\MongoMock\Assert;
33

4+
use Helmich\MongoMock\Log\Query;
45
use Helmich\MongoMock\MockCollection;
5-
use Helmich\MongoMock\Query;
66

77
class QueryWasExecutedConstraint extends \PHPUnit_Framework_Constraint
88
{

src/Functions.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
<?php
22

3-
function executedQuery($filter, $options = [])
3+
use PHPUnit_Framework_Constraint as Constraint;
4+
5+
function collectionExecutedQuery(...$params): Constraint
6+
{
7+
return \Helmich\MongoMock\Assert::collectionExecutedQuery(...$params);
8+
}
9+
10+
function collectionHasIndex(...$params): Constraint
11+
{
12+
return \Helmich\MongoMock\Assert::collectionHasIndex(...$params);
13+
}
14+
15+
function collectionHasDocument(...$params): Constraint
416
{
5-
return \Helmich\MongoMock\Assert::executedQuery($filter, $options);
17+
return \Helmich\MongoMock\Assert::collectionHasDocument(...$params);
618
}

src/Log/Index.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
namespace Helmich\MongoMock\Log;
3+
4+
class Index
5+
{
6+
private $key;
7+
/**
8+
* @var array
9+
*/
10+
private $options;
11+
12+
public function __construct($key, array $options = [])
13+
{
14+
$this->key = $key;
15+
$this->options = $options;
16+
}
17+
}

src/Query.php renamed to src/Log/Query.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?php
2-
namespace Helmich\MongoMock;
2+
namespace Helmich\MongoMock\Log;
33

44
class Query
55
{

src/MockCollection.php

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,48 @@
11
<?php
22
namespace Helmich\MongoMock;
33

4+
use Helmich\MongoMock\Log\Index;
5+
use Helmich\MongoMock\Log\Query;
6+
use MongoDB\BSON\Binary;
7+
use MongoDB\BSON\ObjectID;
48
use MongoDB\Collection;
59
use MongoDB\Model\BSONDocument;
610

711
class MockCollection extends Collection
812
{
913
public $queries = [];
10-
1114
public $documents = [];
1215
public $indices = [];
16+
public $dropped = false;
1317

1418
/** @noinspection PhpMissingParentConstructorInspection */
1519
public function __construct()
1620
{
17-
1821
}
1922

2023
public function insertOne($document, array $options = [])
2124
{
25+
if (!isset($document['_id'])) {
26+
$document['_id'] = new ObjectID();
27+
}
28+
29+
if (!$document instanceof BSONDocument) {
30+
$document = new BSONDocument($document);
31+
}
32+
2233
$document = new BSONDocument($document);
2334
$this->documents[] = $document;
35+
36+
return new MockInsertOneResult($document['_id']);
37+
}
38+
39+
public function insertMany(array $documents, array $options = [])
40+
{
41+
$insertedIds = array_map(function($doc) use ($options) {
42+
return $this->insertOne($doc, $options)->getInsertedId();
43+
}, $documents);
44+
45+
return new MockInsertManyResult($insertedIds);
2446
}
2547

2648
public function deleteMany($filter, array $options = [])
@@ -95,11 +117,9 @@ public function find($filter = [], array $options = [])
95117

96118
public function findOne($filter = [], array $options = [])
97119
{
98-
$matcher = $this->matcherFromQuery($filter);
99-
foreach ($this->documents as $doc) {
100-
if ($matcher($doc)) {
101-
return $doc;
102-
}
120+
$results = $this->find($filter, $options);
121+
foreach ($results as $result) {
122+
return $result;
103123
}
104124
return null;
105125
}
@@ -118,20 +138,16 @@ public function count($filter = [], array $options = [])
118138

119139
public function createIndex($key, array $options = [])
120140
{
121-
$this->indices[] = ['key' => $key, 'options' => $options];
141+
$this->indices[] = new Index($key, $options);
122142
}
123143

124-
public function hasIndex($key, array $options = [])
144+
public function drop(array $options = [])
125145
{
126-
foreach ($this->indices as $index) {
127-
if ($index == ['key' => $key, 'options' => $options]) {
128-
return true;
129-
}
130-
}
131-
return false;
146+
$this->documents = [];
147+
$this->dropped = true;
132148
}
133149

134-
private function matcherFromQuery(array $query)
150+
private function matcherFromQuery(array $query): callable
135151
{
136152
$matchers = [];
137153

@@ -149,12 +165,24 @@ private function matcherFromQuery(array $query)
149165
};
150166
}
151167

152-
private function matcherFromConstraint($constraint)
168+
private function matcherFromConstraint($constraint): callable
153169
{
154170
if (is_callable($constraint)) {
155171
return $constraint;
156172
}
157173

174+
if ($constraint instanceof \PHPUnit_Framework_Constraint) {
175+
return function($val) use ($constraint): bool {
176+
return $constraint->evaluate($val, '', true);
177+
};
178+
}
179+
180+
if ($constraint instanceof ObjectID) {
181+
return function($val) use ($constraint): bool {
182+
return ("" . $constraint) == ("" . $val);
183+
};
184+
}
185+
158186
if (is_array($constraint)) {
159187
return function($val) use ($constraint): bool {
160188
$result = true;
@@ -177,6 +205,10 @@ private function matcherFromConstraint($constraint)
177205
}
178206

179207
return function($val) use ($constraint): bool {
208+
if ($val instanceof Binary && is_string($constraint)) {
209+
return $val->getData() == $constraint;
210+
}
211+
180212
return $val == $constraint;
181213
};
182214
}

0 commit comments

Comments
 (0)