One of the smartest, easier things you can do early on during the development of a function is to treat the "core" of the function body as interface-agnostic. Write a function that just takes an object and returns an object, then write an Adapter function which accepts, say, a lambda event, calls your core handler, and returns whatever your lambda function needs (API Gateway response, etc).
This enables you to, with a little more effort, swap out the Interface Adapter part with, say, a CLI. Or, if you ever want to get off lambda, its a bit easier as well.
Mocking out dependencies, like S3 buckets, isn't worth it during a prototype/MVP. As time goes on, sure, go for it. But early on, just use AWS. Don't use localstack or any of the other various tools that try to replicate AWS. They're all going to get it wrong, hopefully in obvious ways, but usually in subtle ways that'll crash production, and you're just creating more work for yourself. Just use AWS. Just use AWS. Just use AWS.
The agnostic core part is pretty much what I do. Mocking the interfaces is just done to put together the different cores in integration tests and check that the system itself works, independently of what it relies upon.
Then, everything is once more tested using AWS. I stay away from replicating AWS services locally.
It greatly simplifies refactoring and overhauling the core itself, as well as trying out new approaches.
This enables you to, with a little more effort, swap out the Interface Adapter part with, say, a CLI. Or, if you ever want to get off lambda, its a bit easier as well.
Mocking out dependencies, like S3 buckets, isn't worth it during a prototype/MVP. As time goes on, sure, go for it. But early on, just use AWS. Don't use localstack or any of the other various tools that try to replicate AWS. They're all going to get it wrong, hopefully in obvious ways, but usually in subtle ways that'll crash production, and you're just creating more work for yourself. Just use AWS. Just use AWS. Just use AWS.