sql

All things SQL and JDBC...
2021-06-13T03:27:50.188100Z

How to mock a database, for example, with next.jdbc? Do we extend some protocol with test specific methods or ?

seancorfield 2021-06-13T05:10:49.189Z

@i That's a pretty broad question. My recommendation -- generally -- is to use a real DB locally (or in a VM) that you can setup and teardown as needed.

➕ 1
seancorfield 2021-06-13T05:12:18.189800Z

If you have separated your side effecting code from your pure logic, you can mock the side effecting functions (not the DB itself).

indy 2021-06-13T10:18:51.192900Z

What we usually do is to have a real test db like Sean suggests. A lot of times you really need it for end to end tests. We have a macro that will create a transaction from the test-db-spec and wrap the test's body, inside which, you can use this transaction wherever you usually pass a db-spec or transaction or connection. The macro will wrap the body in a try, finally block and in the finally block will rollback the transaction. Example:

(with-test-txn [txn (test-db-spec)]
  (testing "some test"
    (write-answer-to-db txn 42)
    (is (= 42 (get-answer-from-db txn))

2021-06-13T15:40:19.193800Z

@kslvsunil Thanks. The example you give is about testing the (write-to-db) function. How do you test (save-user-requirements) as mentioned in this thread?

indy 2021-06-13T15:53:05.194Z

The save-user-requirements that you broke down looks good. This is usually how I write domain logic for a vertical. The ns usually has one entry function acting as the interface to the outer world, that in turn calls various pure and impure functions. The pure functions can be easily unit tested and the interfacing function is end to end tested with data setup in the test db. Sometimes the impure functions can be mocked like when they're calling external APIs or if it is a DB use a test DB. Since (save-user-requirements) is internally calling the DB, you could with-redef the function that gets you your db-spec to return the test-db-spec instead.

indy 2021-06-13T15:55:58.194200Z

But I'd love to hear how really experienced folks like Sean do it.

2021-06-13T15:58:32.194500Z

How do you test the function that invokes the interfacing function, for example, suppose the (my-outer-function) calss (save-user-requirements). Do you with-redef (save-user-requirements) ?

2021-06-13T15:58:49.194700Z

s/calss/ calls

indy 2021-06-13T16:21:56.194900Z

You want to test my-outer-function? It depends on your assertions and what you're confident with. If you want to test the side effect of save-user-requirements then you should use a test-db. If you're fairly confident that it will work, you can with-redef it.

2021-06-13T05:45:41.189900Z

Using a real local DB seems to testing the db connection functionality itself.

2021-06-13T05:46:26.190100Z

Say I have a (save-user-requirements xxx) function, which internally uses (save-to-db! db) function. I need to mock the save-to-db! function into something without communicating with the real db.

2021-06-13T05:51:07.190300Z

I think when I am testing (save-to-db!) function itself, I need use some real db. But when I am testing the (save-user-requirement) function, I need find a way to properly mock out the (save-to-db!) function.

2021-06-13T05:51:34.190500Z

So basically I found (with-redefs) and (defprotocol) to mechanism to alter (save-to-db!), I am not sure which one is more common.

seancorfield 2021-06-13T05:59:41.190700Z

Your problem is that your save-user-requirements domain logic function calls a side-effecting function.

seancorfield 2021-06-13T06:00:14.190900Z

Separate those and you don't have to worry about "testing the db".

seancorfield 2021-06-13T06:01:15.191100Z

What does save-user-requirements do that needs testing (aside from the "trivial" persistence function that shouldn't need testing, right)?

seancorfield 2021-06-13T06:02:31.191300Z

If you're going to mix logic and persistence, use a real DB and live with the consequences. Otherwise, properly separate your logic from your persistence and just test your logic.

2021-06-13T06:11:52.191500Z

Yes. I want to go with the second approach. This should save much hassle.

2021-06-13T06:14:06.191700Z

Do you think this is an anti-pattern?

2021-06-13T06:14:08.191900Z

;; trasforms the data
(defn- save-user-requirements-impl [data])

(defn save-user-requirements [data db]
  (-> data
      save-user-requirements-impl
      (write-to-db db)))

2021-06-13T06:15:33.192100Z

If I separate the persistence logic from the (pure) business one. Then all the save-user-requirement-alike function skeleton will look like the above

2021-06-13T06:17:23.192300Z

Kinda like some contrived repetitive code structure, designed in a specific way for the testing purpose only.

seancorfield 2021-06-13T06:18:39.192500Z

Right, which should suggest that you need to think more broadly about how you make this separation at all levels.

2021-06-13T06:20:03.192700Z

Yes. I’ve been thinking about a general clojure application layout containing external state interactions (such as db, web) for some time but couldn’t settle down on something natural to me. Do you have any good codebase for reference?