Mockery¶
Expectation¶
Lưu ý: Để các expectation
được thực hiện chúng ta phải gọi đến hàm Mockery::close()
, tối nhất nó nên được để trong một callback method như teardown
hoặc _after
(tùy thuộc vào việc ta kết hợp Mockery với framework nào. Với Laravel là teardown
). Lệnh này dọn dẹp vùng chứa Mockery được sử dụng bởi hàm test hiện tại và sẽ chạy bất kỳ tác vụ nào cho expectation
Khi đã tạo một mock object nghĩa là chúng ta muốn xác định chính xác cách nó hoạt động (nó được gọi như thế nào). Đây chính là việc định nghĩa một expectation
Phương thức¶
Để nói với test chúng ta sẽ thực hiện gọi một method với tên chỉ định, sử dụng phương thức shouldReceive
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method');
expectation
mà dựa vào đó tất cả các kỳ vọng
ràng buộc khác được thêm vào.
Chúng ta có thể định nghĩa nhiều method
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method_1', 'name_of_method_2');
expectation
cùng với giá trị mà nó trả về
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive([
'name_of_method_1' => 'return value 1',
'name_of_method_2' => 'return value 2',
]);
$mock = \Mockery::mock('MyClass', [
'name_of_method_1' => 'return value 1',
'name_of_method_2' => 'return value 2'
]);
$mock = \Mockery::mock('MyClass');
$mock->shouldNotReceive('name_of_method');
shouldReceive()->never()
Tham số¶
Với mọi phương thức khai báo kỳ vọng, chúng ta có thể thêm kỳ vọng về tham số được truyền vào:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->with($arg1, $arg2, ...);
// or
$mock->shouldReceive('name_of_method')
->withArgs([$arg1, $arg2, ...]);
matcher class
. Ví dụ phương thức \Mockery::any()
sẽ khớp bất kỳ tham số nào được truyền với with
. Mockery cho phép thư viện Hamcrest
, ví dụ hàm anything()
chính là tương đương \Mockery::any()
.
Một điều quan trọng cần lưu ý, điều này có nghĩa là tất cả các expectation được đính kèm sẽ chỉ apply cho method khi nó gọi chính xác các tham số.
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')->with('Hello');
$mock->foo('Goodbye'); // throws a NoMatchingExpectationException
Match tham số với closure¶
Thay vì cung cấp một trình đối khớp cho từng tham số, ta có thể cung cấp một closure
cho tất cả các tham số được truyền một lúc:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withArgs(closure);
Closure
nhận tất cả các tham số được truyền khi gọi đến phương thức. Bằng cách này expectation
sẽ chỉ được apply cho method có tham số truyền vào thỏa mãn closure
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')->withArgs(function ($arg) {
if ($arg % 2 == 0) {
return true;
}
return false;
});
$mock->foo(4); // matches the expectation
$mock->foo(3); // throws a NoMatchingExpectationException
Match tham số với giá trị định sẵn¶
Chúng ta có thể cung cấp các tham số được mong đợi match với tham số được truyền vào khi một mock method được gọi
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withSomeOfArgs(arg1, arg2, arg3, ...);
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->withSomeOfArgs(1, 2);
$mock->foo(1, 2, 3); // matches the expectation
$mock->foo(3, 2, 1); // matches the expectation (passed order doesn't matter)
$mock->foo('1', '2'); // throws a NoMatchingExpectationException (type should be matched)
$mock->foo(3); // throws a NoMatchingExpectationException
Any / no¶
Chúng ta có thể khai báo rằng expectation
match với bất kỳ tham số nào
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withAnyArgs();
Ngoài ra chúng ta có thể khai báo exptation
match với việc gọi phương thức không có đối số.
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->withNoArgs();
Return value Expectation
¶
Với mock object. chúng ta có thể khai báo với Mockery
kết quả trả về của một method với andReturn()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturn($value);
Có thể thiết lập kỳ vọng cho nhiều giá trị trả về:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturn($value1, $value2, ...)
$value1
và lần gọi tiếp theo sẽ trả về $value2
.
Nếu gọi phương thức nhiều lần hơn số return value mà chúng ta đã khai báo, Mockery sẽ trả về giá trị cuối cùng cho bất kỳ lệnh gọi phương thức tiếp theo nào
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')->andReturn(1, 2, 3);
$mock->foo(); // int(1)
$mock->foo(); // int(2)
$mock->foo(); // int(3)
$mock->foo(); // int(3)
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnValues([$value1, $value2, ...])
Hai cú pháp sau đây sẽ chủ yếu để giao tiếp với người đọc test:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnNull();
// or
$mock->shouldReceive('name_of_method')
->andReturn([null]);
Đôi khi chúng ta muốn tính kết quả trả về, dựa vào các tham số được truyền, khi ấy chúng ta cần dùng andReturnUsing()
. Nó nhận nhiều hơn một closure
.
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnUsing(closure, ...);
andReturn()
.
Đôi khi phương thức sẽ trả về chính một trong các đối số được truyền vào. Khi đó phương thức andReturnArg()
sẽ hữu ích, tham số được trả về chúng là index trong list tham số
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnArg(1);
Lưu ý: Không thể mix andReturnUsing()
hoặc andReturnArg
với andReturn()
Nếu muốn mock fluid interface
, phương thức sau sẽ hữu dụng:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andReturnSelf();
Throw exception¶
Chúng ta có thể giả lập phương thức sẽ throw exception:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andThrow(new Exception);
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->andThrow('exception_name', 'message', 123456789);
Kỳ vọng số lần gọi¶
Bên cạnh việc thiết lập expectation
cho các tham số truyền vào hàm và kết quả trả về của chúng, chúng ta có thể thiết lập kỳ vọng về số lần gọi đến hàm đó.
Khi thiết lập kỳ vọng số lần gọi cho một phương thức không được gọi đến sẽ throw \Mockery\Expectation\InvalidCountException
.
Lưu ý: Phương thức này bắt buộc phải gọi \Mockery::close()
ở cuối test, chẳng hạn như có thể gọi ở phương thức teardown
với PHPUnit. Nếu không Mockery sẽ không xác minh các lệnh gọi đối với mock object (vì thế việc count cũng không thể thực hiện)
Để khai báo phương thức sẽ được gọi 0 hoặc nhiều lần:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->zeroOrMoreTimes();
Để nói với Mockery một số lượng chính xác số lần gọi hàm, ta sẽ sử dụng như sau:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->times($n);
Một vài trường hợp phổ biến sẽ có phương thức gọi trực tiếp.
Định nghĩa method mong đợi được gọi một lần
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->once();
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->twice();
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->never();
Count modifier¶
Mockery bổ sung một số phương thức để thiết lập kỳ vọng cho số lần gọi method
Nếu muốn khai báo số lần tối thiểu một phương thức sẽ được gọi, sử dụng atLeast()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->atLeast()
->times(3);
Tương tự, chúng ta cũng có thể khai báo cho Mockery biết số lần nhiều nhất một phương thức có thể được gọi với atMost()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->atMost()
->times(3);
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method')
->between($min, $max);
between()
chính là việc sử dụng atLeast()->times($min)->atMost()->times($max)
Argument Validation¶
Validate tham số¶
Đây chính là việc match tham số khi tạo một kỳ vọng cho tham số truyền vào phương thức.
Mockery sẽ hỗ trợ thư viện Hamcrest. Các ví dụ dưới đây tìm hiểu về các hàm match của Mockery và hàm tương đồng phía Hamcrest
Lưu ý: Nếu bạn không muốn sử dụng hàm global của Hamcrest thì có thể sử dụng class \Hamcrest\Matchers
. Ví dụ identicalTo($arg)
chính là \Hamcrest\Matchers::identicalTo($arg)
Trình match phổ biến nhất chính là hàm with()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(1):
foo
với tham số kiểu integer
giá trị 1
. Trong tường hợp này, Mockery đầy tiên sẽ thử so sánh với phép ===
. Nếu nó fail phép thử này Mockery sẽ cố gắng fallback với phép so sánh ==
.
Khi thực hiện match một object Mockery chỉ sử dụng phép so sánh ===
.
$object = new stdClass();
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with($object);
// Hamcrest equivalent
$mock->shouldReceive("foo")
->with(identicalTo($object));
instance khác của stdCalss
sẽ không được coi là match.
Lưu ý: Mockery\Matcher\MustBe
sẽ không được sử dụng nữa
Còn nếu bạn chỉ muốn so sánh ==
cho object thì sẽ phải dùng phương thức equalTo
của Hamcrest
$mock->shouldReceive("foo")
->with(equalTo(new stdClass));
any()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::any());
// Hamcrest equivalent
$mock->shouldReceive("foo")
->with(anything())
Validate kiểu dữ liệu¶
Hàm type()
sẽ nhận một chuỗi, chuỗ đó sẽ được ghép vào is_
để tạo thành một phép kiểu tra hợp lệ
Để match bất kỳ PHP resource nào ta sẽ truyền resource
vào hàm type()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::type('resource'));
// Hamcrest equivalents
$mock->shouldReceive("foo")
->with(resourceValue());
$mock->shouldReceive("foo")
->with(typeOf('resource'));
true
từ một is_resoruce
được gọi nếu đối số được truyền vào là một resource của PHP. Ví dụ tiếp để dễ hiểu hơn\Mockery::type('float')
hoặc floatValue()
và typeOf('float')
kiểm tra sử dụng is_float()
, và \Mockery::type('callable')
hay callable()
của Hamcrest sử dụng is_callable()
.
type()
cũng chấp nhận tên của một class hay một interface được sử dụng trong instanceOf
. Hàm tương tự của Hamcrest là anInstanceOf
.
Tham khảo đầy đủ các hàm check tại php.net và các hàm của Hamcrest
Nếu muốn thực hiện match đối số một cách phức tạp hơn, on()
chính là hàm hỗ trợ điều này. Nó chấp nhận anonymous function
là một đối số được truyền vào.
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::on(closure));
true
nghĩa là tham số được giả định khớp với kỳ vọng và ngược lại
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::on(function ($argument) {
if ($argument % 2 == 0) {
return true;
}
return false;
}));
$mock->foo(4); // matches the expectation
$mock->foo(3); // throws a NoMatchingExpectationException
on()
.
Ngoài ra, chúng ta có thể sử dụng phương thức withArgs()
. Closure sẽ kiểm tra các đối số được truyền vào phương thức được kỳ vọng và đối số là khớp nếu closure trả về true.
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->withArgs(closure);
Closue
match cũng hỗ trợ tham số là optional
$closure = function ($odd, $even, $sum = null) {
$result = ($odd % 2 != 0) && ($even % 2 == 0);
if (!is_null($sum)) {
return $result && ($odd + $even == $sum);
}
return $result;
};
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')->withArgs($closure);
$mock->foo(1, 2); // It matches the expectation: the optional argument is not needed
$mock->foo(1, 2, 3); // It also matches the expectation: the optional argument pass the validation
$mock->foo(1, 2, 4); // It doesn't match the expectation: the optional doesn't pass the validation
pattern()
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::pattern('/^foo/'));
// Hamcrest equivalent
$mock->shouldReceive('foo')
->with(matchesPattern('/^foo/'));
ducktype()
là một phương thức để match kiểu của class
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::ducktype('foo', 'bar'));
on()
, không có version Hamcrest nào cho ducktype()
.
Capturing Arguments¶
Nếu chúng ta muốn thực hiện nhiều match cho cùng một đối số, capture
cung cấp một giải pháp để cùng hàm on()
phục vụ điều đó.
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive("foo")
->with(\Mockery::capture($bar));
foo
vào biến$bar
, từ đó ta sẽ bổ sung validation sử dụng assertion.
Bổ sung đối sánh tham số¶
not()
sẽ khớp với bất kỳ đối số nó không bằng hoặc không giống với tham số được truyền vào nó
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::not(2));
// Hamcrest equivalent
$mock->shouldReceive('foo')
->with(not(2));
anyOf()
sẽ match nếu như tham số của expectation
bằng một trong bất kỳ tham số nào của hàm
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::anyOf(1, 2));
// Hamcrest equivalent
$mock->shouldReceive('foo')
->with(anyOf(1,2));
notAnyOf()
sẽ ngược lại với anyOf()
nó sẽ match nếu như tham số của expectation
không bằng bất kỳ tham số nào của phương thức:
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::notAnyOf(1, 2));
notAnyOf()
sẽ không có bên Hamcrest
subset()
sẽ match nếu như tham số là một mảng có chứa mảng đã cho.f
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::subset(array(0 => 'foo')));
Việc này sẽ thực hiện cả trên cả tên biến và giá trị, nó tương ứng với key và value của mảng tham số.
contains()
cũng tương tự như subset()
nhưng sẽ không quan tâm đến tên của key của mảng.
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::contains(value1, value2));
hasKey()
khớp với đối số là một mảng có chứa key name đã cho
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::hasKey(key));
hasValue()
khớp với đối số là một mảng có chứa value đã cho
$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('foo')
->with(\Mockery::hasValue(value));
Spies¶
Spies là một loại test double, tuy nhiên khác với mock ở chỗ nó ghi lại tất cả tương tác giữa nó với hệ thống test (SUT) và cho phép đưa ra các assertion với những tương tác đó sau khi SUT chạy.
Tạo một spy giúp bạn không cần phải thiết lập việc call tất cả các phương thức như mock. Bạn chỉ cần tạo assertion cho việc call một vài phương thức mà bạn quan tâm đến, bởi lẽ không phải phương thức nào cũng ảnh hưởng cho một test case nhất định.
Trong khi với mock
thì hầu như phải áp dụng style Arrange-Act-Assert
vì chúng phải khai báo expect
những hàm được gọi cùng kết quả trả về trước khi gọi hàm thực thi trong test, cuối cùng mới là assert xem những expect đó đã được đáp ứng.
// arrange
$mock = \Mockery::mock('MyDependency');
$sut = new MyClass($mock);
// expect
$mock->shouldReceive('foo')
->once()
->with('bar');
// act
$sut->callFoo();
// assert
\Mockery::close();
spies
có thể áp dụng linh hoạt cả style Arrange-Act-Assert
hoặc Given-When-Then
. Nó cho phép bỏ qua expect và chuyển assertion đến sau act ở SUT, giúp cho test case dễ đọc hiểu hơn.
// arrange
$spy = \Mockery::spy('MyDependency');
$sut = new MyClass($spy);
// act
$sut->callFoo();
// assert
$spy->shouldHaveReceived()
->foo()
->with('bar');
spies
ít hạn chế hơn so với mock
, Nó giúp nêu bật mục đích test và hạn chế lộ cấu trúc của SUT.
Tuy nhiên, hạn chế của spies
là debug. Khi mock bị gọi ngoài expect, nó lập tức throw exception, nó gần như được coi là một trình debug. Với spies nếu có một hàm call sai, nó sẽ không thể có bối cảnh thời gian lập tức như mock
, nó chỉ đơn giản khẳng định một hàm được gọi.
Cuối cùng, spy không thể định nghĩa giá trị return của hàm, chỉ có mock mới làm được điều đó.
Một số hàm thông dụng của spies¶
Để verify một phương thức được gọi trong spy, sử dụng shouldHaveReceived()
$spy->shouldHaveReceived('foo');
shouldNotHaveReceived()
$spy->shouldNotHaveReceived('foo');
spies
ta có thể dùng hai cách: sử dùng hàm with
hoặc sử dụng một mảng gồm các tham số cần cần đối sánh để truyền vào hàm:
$spy->shouldHaveReceived('foo')
->with('bar');
// Or
$spy->shouldHaveReceived('foo', ['bar']);
shouldNotHaveReceived()
. Nếu muốn sử dụng với một hàm không được gọi bạn cần phải sử dụng cách 2:
$spy->shouldNotHaveReceived('foo', ['bar']);
$spy->shouldHaveReceived('foo')
->with('bar')
->twice();
Thay thế cú pháp shouldReceive
¶
Kể từ Mockery 1.0.0, hỗ trợ gọi các phương thức tương tự như các phương thức của PHP mà không cần đối số dạng String
trong should
method
Với spies nó mới chỉ được áp dụng cho shouldHaveReceived()
$spy->shouldHaveReceived()
->foo('bar');
// Expect number of call
$spy->shouldHaveReceived()
->foo('bar')
->twice();
shouldNotHaveReceived()
.