Summer Sale! All accounts are 50% off this week.

untymage's avatar

How to delete instance of Http::fake() in app?

Since it is not possible to disable Http::fake in multilple usage, i want know how to delete its instance ? I mean:

/** @test */
public function example_test()
{
    Http::fake(fn() => ['data' => 1 ]);

    $this->app->forgetInstance(Http::class);

    // new instance with another data then some actions

    Http::fake(fn() => ['data' => 2]);

    // another action here usese the second fake data
}

But it uses the first fake method data, How can i override the Http::fake data ?

0 likes
4 replies
eddie.hale's avatar

This is how I did it. The callbacks are stored in 'stubCallbacks'. So I just cleared the collection of stubs and then afterwards called Http::fake([]).

  public function testExample(){
	Http::fake([...]) ;
	//test stuff

	self::clearExistingFakes();
	Http::fake([...]);
	//test stuff
  } 

  private static function clearExistingFakes(): void{
    $reflection = new \ReflectionObject(Http::getFacadeRoot());
    $property = $reflection->getProperty('stubCallbacks');
    $property->setAccessible(true);
    $property->setValue(Http::getFacadeRoot(), collect());
  }

4 likes
olliescase's avatar

@eddie.hale Thank you for your answer! I know this was from 2 years ago, but it helped me recently as I encountered a need to achieve this.

The problem with setting a sequence is your tests then need to follow that sequence - it makes it much easier to break tests by adding a dependency to the order in which they're ran (imo). Additionally, it feels unnatural in the context of pest tests as the application isn't booted at the point of beforeAll running.

For those who still wish to achieve this; I find a more elegant method is:

function resetHttpFakes() {
    app()->forgetInstance(get_class(Http::getFacadeRoot()));
    Http::clearResolvedInstances();
}

Let me define what I mean by elegant;

  1. Its a smaller code-footprint
  2. It doesn't rely on adjusting visibility of properties
  3. It is more robust towards changes in the Laravel ecosystem.

One negative to this approach and something to consider as to whether you'd want to implement Eddie's changes instead, is this method completely bins the existing object and rebuilds it, whereas Eddie's keeps the original object intact & will only reset the single property. So in that sense, you could say that Eddie's is more elegant; it depends on your perspective & preference :)

The reason why clearResolvedInstances doesn't work is because the facade then attempts to get the object from the app container again, which isn't cleared by this method. So in order for a completely new instance to be given, you must clear it both from the application container and the facade resolved instances.

Hope this helps :)

2 likes

Please or to participate in this conversation.