Clear Cursive REPL state before each test run

753 views Asked by At

I'm new to Cursive and Clojure in general and am having some difficulty getting a decent TDD workflow.

My problem is that subsequent test runs depend on state in the REPL. For example suppose that you have the code below.

(def sayHello "hello")

(deftest test-repl-state
  (testing "testing state in the repl"
      (is (= "hello" sayHello)))) 

If you run this with "Tools->REPL->Run tests in current ns in REPL" it will pass.

If you then refactor the code like this

(def getGreeting "hello")

(deftest test-repl-state
  (testing "testing state in the repl"
      (is (= "hello" sayHello))))  

If you run this with "Tools->REPL->Run tests in current ns in REPL" it will still pass (because the def of sayHello still exists in the repl). However, the tests should fail because the code is currently in a failing state (sayHello is not defined anywhere in the code).

I've tried toggling the "locals will be cleared" button in the REPL window but this does not seem to fix the issue.

If there is a way to run the tests outside of the REPL (or in a new REPL for each test run) I'd be fine with that as a solution.

All I want is that there is a 1 to 1 correspondence between the source code under test and the result of the test.

Thanks in advance for your help.

5

There are 5 answers

2
akond On

You could use Built-in test narrowing (test selector) feature of test-refresh lein plugin. It allows to test only those tests that have been marked with ^:test-refresh/focus meta every time you save a file.

0
Svante On

The usual solution for this kind of problem is either stuartsierra/component or tolitius/mount.

A complete description would be out of place here, but the general idea is to have some system to manage state in a way that allows to cleanly reload the application state. This helps keeping close to the code that is saved in your source files while interactively working on the running system.

0
Carcigenicate On

Yes, it's annoying to have old defs available. I don't even create tests usually (whoops), but this bites me during normal development. If I create a function, then rename it, then change it, then accidentally refer to the first function name, I get odd results since it's referring to the old function. I'm still looking for a good way around this that doesn't involve killing and restarting the REPL.

For your particular case though, there's a couple easy, poor workarounds:

  • Open IntelliJ's terminal (button at bottom left of the window) and run lein test. This will execute all the project's tests and report the results.

  • Similarly to the above, you can, outside of IntelliJ, open a command window in the project directory and run lein test, and it will run all found tests.

You can also specify which namespace to test using lein test <ns here> (such as lein test beings-retry.core-test), or a specific test in a namespace using :only (such as lein test :only beings-retry.core-test/a-test; where a-test is a deftest). Unfortunately, this doesn't happen in the REPL, so it kind of breaks workflow.


The only REPL-based workaround I know of, as mentioned above, is to just kill the REPL:

  • "Stop REPL" (Ctrl+F2)
  • "Reconnect" (Ctrl+F5).

Of course though, this is slow, and an awful solution if you're doing this constantly. I'm interested to see if anyone else has any better solutions.

0
Russell On

Thanks to everyone for their suggestions. I'm posting my own answer to this problem because I've found a way forward that works for me and I'm not sure that any of the above were quite what I was looking for.

I have come to the conclusion that the clojure REPL, although useful, is not where I will run tests. This basically came down to a choice between either running a command to clean the repl between each test run (like the very useful refresh function in tools.namespace https://github.com/clojure/tools.namespace) or not running tests in the REPL.

I chose the latter option because.

  1. It is one less step to do (and reloading is not always perfect)
  2. CI tests do not run in a REPL so running them directly in dev is one step closer to the CI environment.
  3. The code in production does not run in a REPL either so running tests outside the repl is closer to the way that production code runs.

It's actually a pretty simple thing to configure a run configuration in IntelliJ to run either a single test or all tests in your application as a normal clojure application. You can even have a REPL running at the same time if you like and use it however you want. The fact that the tooling leans so heavily towards running things in the REPL blinded me to this option to some extent.

run tests within cursive

I'm pretty inexperienced with Clojure and also a stubborn old goat that is set in his TDD ways but at least some others agree with me about this https://github.com/cursive-ide/cursive/issues/247.

Also if anyone is interested, there is a great talk on how the REPL holds on to state and how this causes all sorts of weird behaviour here https://youtu.be/-RaFcpNiYCo. It turns out that the problem I was seeing with re-defining functions was just the tip of the iceberg.

0
Nick Nichols On

One option that may help, especially if you're bundling several assertions, or have repeating tests is let. The name-value binding has a known scope, and can save you from re-typing a lot.

Here's an example:

(deftest my-bundled-and-scoped-test
   (let [TDD    "My expected result"
         helper (some-function :data)]
     (testing "TDD-1: Testing state in the repl"
       (is (= TDD "MY expected result")))
     (testing "TDD-2: Reusing state in the repl"
       (is (= TDD helper)))))

Once my-bundled-and-scoped test finishes executing, you'll no longer be in the let binding. An added benefit is that the result of some-function will be reusable too, which is handy for testing multiple assertions or properties of the same function/input pair.

While on the subject, I'd also recommend using Leiningen to run your tests, as there are plenty of plugins that can help you test more efficiently. I'd checkout test-refresh, speclj, and cloverage.