diff --git a/public/assets/images/posts/2024/with-consecutive.png b/public/assets/images/posts/2024/with-consecutive.png
new file mode 100644
index 00000000000..901bcaea339
Binary files /dev/null and b/public/assets/images/posts/2024/with-consecutive.png differ
diff --git a/resources/css/app.scss b/resources/css/app.scss
index a0b5b49c7a3..64d1520a51e 100644
--- a/resources/css/app.scss
+++ b/resources/css/app.scss
@@ -152,6 +152,11 @@ img {
}
#post {
+ h1 {
+ // magic :)
+ text-wrap: balance;
+ }
+
img {
text-align: center;
margin: .5em auto 1.5em auto;
diff --git a/resources/posts/2024/2024-10-08-how-to-upgrade-deprecated-phpunit-with-consecutive.md b/resources/posts/2024/2024-10-08-how-to-upgrade-deprecated-phpunit-with-consecutive.md
new file mode 100644
index 00000000000..667311a026b
--- /dev/null
+++ b/resources/posts/2024/2024-10-08-how-to-upgrade-deprecated-phpunit-with-consecutive.md
@@ -0,0 +1,231 @@
+---
+id: 417
+title: "How to Upgrade deprecated PHPUnit withConsecutive()"
+perex: |
+ The `withConsecutive()` method [was deprecated in PHPUnit 9](https://github.com/sebastianbergmann/phpunit/issues/4255#issuecomment-636422439) and removed in PHPUnit 10. It sparked many [questions](https://stackoverflow.com/questions/75389000/replace-phpunit-method-withconsecutive-abandoned-in-phpunit-10), [on StackOverflow](https://stackoverflow.com/questions/77865216/phpunit-withconsecutive-is-gone-what-is-the-recommended-approach), in [various projets](https://www.drupal.org/project/drupal/issues/3306554) and [GitHub](https://github.com/search?q=repo%3Asebastianbergmann%2Fphpunit+withConsecutive&type=issues).
+
+ It was not very popular BC break. There is no 1:1 replacement. It can be combined with `willReturn*()` methods and that can make it even more tricky to merge with.
+
+ PHPUnit upgrades take 95 % of time just to upgrade this single method, and 5 % about everything else.
+ Recent months we've done couple of project upgrades with Rector and we've learned a lot.
+
+ Today, I want to share bit of knowledge with you and explain, **why it's a change for better code**.
+---
+
+What does `withConsecutive()` method actually do?
+
+```php
+$mock = $this->createMock(MyClass::class);
+$mock->expects($this->exactly(2))
+ ->method('someMethod')
+ ->withConsecutive(
+ ['first'],
+ ['second']
+ );
+```
+
+It defines what arguments are on the input, once the method mock is called. E.g. here:
+
+* on 1st call, it expects `['first']`
+* on 2nd call, it expects `['second']`
+
+To be honest, I've never wrote such code myself, but so far we found it in every code base we've upgraded.
+It's been available since 2006, and only removed after 16 years in 2022.
+
+
+
+So how can be replace it? It would be very convenient, if there would some kind of `withNthCall()` method:
+
+```php
+$mock = $this->createMock(MyClass::class);
+$mock->expects($this->exactly(2))
+ ->method('someMethod')
+ ->withNthCall(1, ['first'])
+ ->withNthCall(2, ['second']);
+```
+
+But it's not.
+
+
+## `withCallable()` to the Rescue
+
+Instead we use `withCallable()` trick. This methods accepts the called parameters, that we can assert inside.
+
+```php
+$mock = $this->createMock(MyClass::class);
+$mock->expects($this->exactly(2))
+ ->method('someMethod')
+ ->withCallable(function ($parameters) {
+ // check the parameters here
+ });
+ ```
+
+But how do we detect, if it's the 1st or 2nd call? The `$this->exactly(2)` expression actually returns a value object `PHPUnit\Framework\MockObject\Rule\InvokedCount` that we can work with.
+
+```php
+$invokedCount = $this->exactly(2);
+
+$mock = $this->createMock(MyClass::class);
+$mock->expects($invokedCount)
+ ->method('someMethod')
+ ->withCallable(function ($parameters) use ($invokedCount) {
+ // check the parameters here
+ });
+```
+
+On every method mock invoke, the number of invokes in `$invokedCount` will get increased.
+
+
+
+We can use it to detect the 1st or 2nd call:
+
+```php
+$invokedCount = $this->exactly(2);
+
+$mock = $this->createMock(MyClass::class);
+$mock->expects($invokedCount)
+ ->method('someMethod')
+ ->withCallable(function ($parameters) use ($invokedCount) {
+ if ($invokedCount->getInvocationCount() === 1) {
+ // check the 1st round here
+ }
+
+ if ($invokedCount->getInvocationCount() === 2) {
+ // check the 2nd round here
+ }
+ });
+```
+
+Now we include original parameters we needed it:
+
+```php
+// ...
+
+ ->withCallable(function ($parameters) use ($invokedCount) {
+ if ($invokedCount->getInvocationCount() === 1) {
+ $this->assertSame(['first'], $parameters);
+ }
+
+ if ($invokedCount->getInvocationCount() === 2) {
+ $this->assertSame(['second'], $parameters);
+ }
+ });
+```
+
+Now this where this deprecation becomes useful. What if one of parameters is an product object?
+
+We could create a `$product` object and do `assertSame()`. But what if we only care about its price?
+
+```php
+// ...
+ ->withCallable(function ($parameters) use ($invokedCount) {
+ if ($invokedCount->getInvocationCount() === 1) {
+ $product = $parameter[0];
+ $this->assertInstanceof(Product::class, $product);
+ $this->assertSame(100, $product->getPrice());
+ }
+
+ // ...
+ });
+```
+
+This would turn into single-line mess using `withConsecutive()`. Now it's more readable and flexible.
+
+
+
+## Why `if` over `match`?
+
+Originally, we used `match()` expression over `ifs()` in Rector rule, but it created couple of new problems:
+
+* PHPUnit 9.x requires PHP 7.3+. Using `match()` would mean you have to do the upgrade to PHP 8 and to PHPUnit 10 at the same time. This is not always possible and can be risky
+* The call count is already checked by `$this->exactly(2)`. There is no need to add another layer of complexity to check the same thing again
+* With `match()` there is only single line of expression. Assert above would be single line:
+
+```php
+=> $product = $parameters[0] && $this->assertInstanceof(Product::class, $product) && $this->assertSame(100, $product->getPrice())
+ ```
+
+Which is not readable and maintainable. There is also one more reason why `if()` is the king.
+
+
+## Return value
+
+More often than not, the method not only accepts parameters, but also return some value. That's where `willReturn*()` methods come into play:
+
+```php
+$mock = $this->createMock(MyClass::class);
+$mock->expects($this->exactly(2))
+ ->method('someMethod')
+ ->withConsecutive(
+ ['first'],
+ ['second']
+ )
+ ->willReturnOnConsecutiveCalls([1, 2]);
+```
+
+How do we upgrade any returned value? We just return it:
+
+```php
+// ...
+
+ ->withCallable(function ($parameters) use ($invokedCount) {
+ if ($invokedCount->getInvocationCount() === 1) {
+ $this->assertSame(['first'], $parameters);
+ return 1;
+ }
+
+ if ($invokedCount->getInvocationCount() === 2) {
+ $this->assertSame(['second'], $parameters);
+ return 2;
+ }
+ });
+```
+
+That's it! How about other return methods?
+
+```php
+->willReturnArgument(0);
+// or
+->willReturnSelf();
+// or
+->willThrowException(new \Exception('Never happens'));
+```
+
+We can just write plain PHP code:
+
+```php
+// ...
+
+ ->withCallable(function ($parameters) use ($invokedCount) {
+ if ($invokedCount->getInvocationCount() === 1) {
+ return $parameters[0];
+ // or
+ return $this->userServiceMock;
+ // or
+ throw new Exception('Never happens');
+ }
+ });
+```
+
+## More Readable and Easier to Maintain
+
+* We don't have to learn special PHPUnit mock method naming and we can understand the code.
+* This vanilla PHP also opens up next step - refactoring [away from mocks to anonymous typed classes](/blog/2018/06/11/how-to-turn-mocks-from-nightmare-to-solid-kiss-tests)
+* We can easily add new assertion line
+* We can return values we need
+
+
+What if in upcoming PHPUnit 12, 13, 14... versions some of mocking methods will be changed or removed? This code will most likely work, as it's just plain PHP.
+
+
+
+This is how we can upgrade `withConsecutive()` method in PHPUnit 9 or earlier. I hope it's more clear now why this change was needed, and how it can help you write better tests.
+
+
+
+Last but not least, here is [the Rector rule](https://getrector.com/rule-detail/with-consecutive-rector) that automated this process.
+
+
+
+
+Happy coding!