I Stopped Writing Unit Tests for Lambda Handlers. Here's What Broke
I used to write a unit test for every Lambda handler. Then I looked at what they were actually asserting and realized most of them were testing the mock, not the code. This article covers what I stopped doing, what I do instead (extract the logic, test that hard; test the handler against something real), and the three cases where I was wrong about dropping handler tests entirely. The honest position is narrower than the headline: most handler tests test the mock.
For two years I wrote a unit test for every Lambda handler. Mocked the AWS SDK, mocked the event, asserted the response. Good coverage numbers. Green pipeline.
Then I actually looked at what those tests were checking, and I didn't like the answer.
What I was actually testing
Here's a handler test I used to write, roughly:
Mock DynamoDB so getItem returns a fake record. Mock S3 so putObject succeeds. Call the handler with a fake event. Assert the return value is a 200 with the right body.
Every one of those assertions tests my mock. I'm checking that my fake DynamoDB returns what I told it to return. If the real SDK changes shape, my mock doesn't know. If the real API rejects my request format, my mock doesn't know. If IAM blocks the call, my mock definitely doesn't know.
I had 90% coverage on handlers and near-zero coverage on the thing that actually broke in production, which was always one of: the request format, the permission, or the timeout.
What I do instead
Extract the logic, test the logic hard. Everything that isn't glue goes into a pure function or a module that doesn't import the AWS SDK. That code gets real unit tests, real edge cases, real fast feedback. It's usually 70% of the handler anyway.
Test the handler against something real. Not necessarily full AWS, but not a hand-rolled mock either. A local emulator or a thin real-AWS smoke test. The point isn't coverage, it's catching the request shape and permission mistakes that mocks are structurally incapable of catching.
Test the integration path once, end to end. One test that goes through the actual entry point with the actual permissions. Slow, but it's the only test that would have caught the failures I actually had.
The handler itself is glue. Glue doesn't need 12 assertions. It needs to be thin enough that there's nothing to assert.
The part where I was wrong
I said I stopped writing handler tests entirely. That's not quite true, and the exception matters.
Handlers with non-trivial branching need tests. If the handler parses an event and routes to one of six paths based on the payload shape, that routing logic is real logic and it belongs in a test. My mistake wasn't testing handlers, it was testing the ones that had nothing to test.
Handlers with auth logic need tests. Anything that decides who can do what is security-relevant code and deserves scrutiny, mocked or not.
Handlers wrapping a third-party API need tests. Not because the test is meaningful, but because the error handling around a flaky external call is genuinely complex and easy to get wrong.
So the honest version of my position is narrower than the headline: most handler tests are testing the mock, and that's not worth the maintenance. Not "don't test handlers."
What I actually gained
Dropping the ceremonial tests cut my test suite runtime by about 70%. That sounds like a nicety until you realize it's the difference between running tests on every save and running them before you push. The suite that runs on every save catches bugs. The suite you run reluctantly doesn't.
The other gain was subtler. When I stopped writing handler tests, I started noticing how much logic was in handlers that shouldn't be. The test was compensating for the structure. Remove the test, and the structural problem becomes obvious.
The question I'd actually want answered
Everyone says "test behavior, not implementation." Almost nobody agrees on where the line is for serverless, because the handler is the implementation and the behavior lives behind it.
So: where do you draw it? Specifically, what's your ratio of mocked handler tests to real-integration tests, and has that ratio changed in the last two years?
I'm genuinely unsure whether my current split is right or just right for the systems I've worked on. If you've got a handler with real branching logic and no unit test, I want to know how you're confident in it.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article