Chased a stupid problem all day today. Long story short: I had a simple Laravel controller that was trying to just echo back the inputs from a JSON request body. It's not enough to write a test like the following.
Wrong Code:
class MyTest extends TestCase {
function test_example() {
$this->withHeaders([
'Accept' => 'application/json',
'Content-Type' => 'application/json',
])->post('echo', ['foo' => 'bar'])->assertJson(['foo' => 'bar']);
}
}
The controller tries to read $request->all(), which is empty! That's because of how the Laravel test framework constructs request objects. It doesn't care that I provided those headers to indicate that there was a JSON body to draw from. You have to use postJson instead of post for that, and then you don't need those headers, either.
Correct:
function test_example() {
$this->postJson('echo', ['foo' => 'bar'])->assertJson(['foo' => 'bar']);
}
The same is true for patch vs. patchJson. So somehow, I've been using the wrong method all this time, and it's never been a problem? I'm shocked and confused.
Top comments (1)
This is such a classic Laravel testing “gotcha” — thanks for writing it up.
What’s really happening here (and what surprised me too) is that post() always treats the payload as form data, regardless of headers, while postJson() actually serializes the body and hydrates the request as JSON.
So even if Content-Type: application/json is set, the test request builder never populates the JSON bag unless you explicitly use *Json() helpers.
Mental model that helped me after getting burned once:
post() = form request, postJson() = JSON request — headers don’t change that.
Super valuable reminder. I’m sure this will save someone hours of debugging.