Bạn đang gặp khó khăn khi viết unit test cho các class có các method cần được mock với nhiều cấu hình khác nhau? Bài viết này sẽ hướng dẫn bạn cách sử dụng PHPUnit Mock để cấu hình một method duy nhất thực hiện các hành vi khác nhau dựa trên các tham số đầu vào khác nhau. Chúng ta sẽ khám phá các phương pháp khác nhau, cung cấp ví dụ cụ thể và giúp bạn viết các unit test mạnh mẽ và hiệu quả hơn.
Trong quá trình viết unit test, bạn có thể gặp phải tình huống cần mock một method duy nhất với các hành vi khác nhau dựa trên các tham số đầu vào khác nhau. Ví dụ: bạn có một class `Context` với method `offsetGet()`. Bạn muốn khi gọi `offsetGet('Matcher')` thì trả về một đối tượng `Matcher`, còn khi gọi `offsetGet('Logger')` thì trả về một đối tượng `Logger`.
Vấn đề là, khi bạn cố gắng cấu hình mock như vậy bằng cách sử dụng các method `expects()` và `will()` thông thường của PHPUnit, cấu hình thứ hai thường ghi đè cấu hình thứ nhất. Điều này dẫn đến việc test của bạn không hoạt động như mong đợi.
->at($x)
Một giải pháp là sử dụng method `at($x)` để chỉ định thứ tự chính xác của các lần gọi method. Ví dụ:
$context = $this->getMockBuilder('Context')->getMock();
$context->expects($this->at(0))
->method('offsetGet')
->with('Matcher')
->will($this->returnValue(new Matcher()));
$context->expects($this->at(1))
->method('offsetGet')
->with('Logger')
->will($this->returnValue(new Logger()));
Trong ví dụ này, chúng ta chỉ định rằng lần gọi `offsetGet()` đầu tiên phải có tham số `'Matcher'` và trả về một đối tượng `Matcher`, và lần gọi thứ hai phải có tham số `'Logger'` và trả về một đối tượng `Logger`.
Giải pháp này hoạt động, nhưng nó có một số nhược điểm. Thứ nhất, nó làm cho test của bạn phụ thuộc vào thứ tự gọi method. Nếu thứ tự gọi method thay đổi, test của bạn sẽ bị fail. Thứ hai, nó không hoạt động nếu bạn có nhiều hơn một lần gọi đến mỗi method.
returnCallback
Một giải pháp tốt hơn là sử dụng method `returnCallback()` để chỉ định một callback function để trả về giá trị dựa trên các tham số đầu vào. Ví dụ:
$context = $this->getMockBuilder('Context')->getMock();
$context->expects($this->exactly(2))
->method('offsetGet')
->with($this->logicalOr(
$this->equalTo('Matcher'),
$this->equalTo('Logger')
))
->will($this->returnCallback(function($param) {
if ($param == 'Matcher') {
return new Matcher();
} else if ($param == 'Logger') {
return new Logger();
}
}));
Trong ví dụ này, chúng ta chỉ định rằng method `offsetGet()` có thể được gọi với tham số `'Matcher'` hoặc `'Logger'`. Sau đó, chúng ta sử dụng `returnCallback()` để chỉ định một callback function trả về một đối tượng `Matcher` nếu tham số là `'Matcher'` và một đối tượng `Logger` nếu tham số là `'Logger'`.
returnValueMap()
(PHPUnit 3.6+)
Nếu bạn đang sử dụng PHPUnit 3.6 trở lên, bạn có thể sử dụng method `returnValueMap()` để trả về các giá trị khác nhau dựa trên các tham số khác nhau. Ví dụ:
$context = $this->getMockBuilder('Context')->getMock();
$map = [
['Matcher', new Matcher()],
['Logger', new Logger()]
];
$context->expects($this->any())
->method('offsetGet')
->will($this->returnValueMap($map));
Trong ví dụ này, chúng ta tạo một mảng `$map` chứa các cặp tham số và giá trị trả về tương ứng. Sau đó, chúng ta sử dụng `returnValueMap()` để chỉ định rằng method `offsetGet()` sẽ trả về giá trị tương ứng với tham số trong mảng `$map`.
returnValueMap()
Đảm bảo rằng bạn chỉ định *tất cả* các tham số mà method cần, nếu không nó có thể không hoạt động đúng cách. Tham khảo Github Issue #89 để biết thêm chi tiết.
Bài viết này đã trình bày các phương pháp khác nhau để cấu hình PHPUnit Mock cho một method với các tham số khác nhau. Tùy thuộc vào yêu cầu cụ thể của bạn, bạn có thể chọn phương pháp phù hợp nhất. Sử dụng `returnCallback()` hoặc `returnValueMap()` thường là lựa chọn tốt hơn so với `at($x)` vì chúng linh hoạt hơn và ít phụ thuộc vào thứ tự gọi method.
Bài viết liên quan