-
I'm just starting to learn / work with river and I have a question about how to test. Here's my set up:
On app start, I create the river client and pass it around to the modules which need to insert jobs. No concerns there, the app seems to work fine. However, I'm not sure how to (or if I can at all) get my tests to pass since I can't create a client from the mock pgx pool. I have been considering switching to dockertest for other reasons and that may solve the problem (but also may not) -- however, I'm hoping there's a way to reduce the initial scope. Related question -- is it better to create the client and pass it as needed or create a client on the fly? On the flip side, passing it forces you to pass it like I tried peeking at how river itself is running its tests and I saw the internal/rivertest package. However, I'm not able to use that to create pools/clients. |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 3 replies
-
Okay, first of all, I'd very, very, VERY much recommend not mocking your database. It might seem like a good idea at first glance because it's marginally faster, but there is just far too many details on the interactions between your app and its data store for this to be an effective testing strategy. Not only will mocking everything out be painful, but you'll find before too long that you might as well not be writing tests at all because they're not exercising anywhere near enough of your stack to be be effective. You're basically setting a whole bunch of mocks, and then testing that you set your mocks in the way you expected to set them. Nothing is really being tested. Just as a very basic example, a database mock will be perfectly happy to select values out of a column that doesn't exist. There are 10,000 more examples of that, all the way from a simple missing column/table, all the way up to more complex things like failing FKs, unique constraints, or checks. Instead, just save to a real database. It might seem too slow, but it's not. Databases are fast and you can keep your tests very fast too. Use a technique like test transactions to isolate test cases from each other, but still have them running heavily in parallel. If I can't convince you otherwise, what you can do is create your own mock interface for River's inserts: type RiverInserter[TTx any] interface {
Insert(ctx context.Context, args JobArgs, opts *InsertOpts) (*rivertype.JobInsertResult, error)
InsertTx(ctx context.Context, tx TTx, args JobArgs, opts *InsertOpts) (*rivertype.JobInsertResult, error)
} Pass a
It's pretty much always better to instantiate fewer times if you can manage it. River client is a fairly heavy object so try to only make one of them.
Yeah that's an internal package that sees regular API changes and isn't stable. There's a public |
Beta Was this translation helpful? Give feedback.
-
Thank you for the guidance!
Yes, I stopped writing tests with the mock pretty quickly as it was tedious, fairly brittle, and I started to question how effective the strategy was. I recognized I needed an alternate strategy but wasn't sure what that looked like. You've sold me on using a real db. My follow up questions are:
Random aside -- I'm going to take some time and go through your writings. Seems like a lot of great nuggets of wisedom there that I don't quite grok yet. Thank you for making your knowledge and expertise available to the community. 🙏 |
Beta Was this translation helpful? Give feedback.
-
@dbhoot I've got a dockertest setup that I use for one of projects (but it is in a private repo so I can't show you), and it is "decently fast". On my laptop I get the following:
If there is interest, I can create a PR with my sample setup. My take on dockertest is that it is useful when you have "random people" contributing, and you want to make it easy for them to run local testing with "zero setup" before they submit a PR, but as @brandur pointed out, Docker can add a lot of overhead and headache, so YMMV. |
Beta Was this translation helpful? Give feedback.
-
@brandur I finally got around to implementing the testing strategy using your suggestions as a template. It's glorious! I'm so pleased with the result / outcome. Thanks again for the guidance. 🙏 |
Beta Was this translation helpful? Give feedback.
So I was I had a simpler one, but the best I can offer you right now is River's
riverinternaltest
.River's internal test suite has two test strategies:
A "test DB manager" which spins up a pool of River databases and then has a manager that selects between them, truncates all tables, and then hands off a database to a particular test that needs one.
Use of a single shared test database along with
TestTx
. Tests run in a test transaction so changes are automatically rolled back after each one and so no state needs cleaning up.I'd recommend avoiding (1): River makes us…