The argument that an interface with one implementation is waste ignores that the test double is a second implementation, and the argument that the double justifies it ignores what the double actually does.
interface OrderRepository
{
public function find(OrderId $id): ?Order;
public function save(Order $order): void;
}
// the fake, which is the second implementation
final class InMemoryOrders implements OrderRepository { /* ... */ }
// and the contract test both must pass, which is the part
// that makes the fake trustworthy
abstract class OrderRepositoryContract extends TestCase { /* ... */ }
The interface earns its place only if the fake is real enough to be worth having, and that requires a shared contract test — otherwise the fake diverges and the tests pass against behaviour the database does not have. We had eleven repository interfaces and four fakes; the seven without fakes were interfaces for the sake of it and were deleted.