Raising exceptions in mocks

I have a function that reads JSON from a file:

(define (extract-directives filename #:file-input [with-input-from-file with-input-from-file])
  (define ignore (with-handlers ([exn:fail:filesystem? (lambda (exn) (ignore null (hasheq) (hasheq)))])
                        (with-input-from-file filename
                          (lambda () (read-json))))))

NB I've chopped some of the function out for brevity.
Now what I want to do is use a mock to raise an exception as if the file can't be found:

(module+ test
  (define test-settings (hash 'ignore-file "settings.json"))
  (test-case "extract-directives returns an empty hash if the file can't be read"
    (define mock-file-read (mock #:behavior (const (raise (exn:fail:filesystem:exists "test" (current-continuation-marks))))))
    (define result (extract-directives test-settings #:file-input mock-file-read))
    (check-true (hash-empty? result)))

However, instead of the flow of control, I'm expecting (i.e. the mock raises an exception then with-handlers catches it) I get:

extract-directives returns an empty hash if the file can't be read
; ERROR


test

So I'm confused about how best to simulate errors without handing bad data to the function. How can I do this better?

1 Like

The error occurs here:

(define mock-file-read (mock #:behavior (const (raise (exn:fail:filesystem:exists "test" (current-continuation-marks))))))

const is a regular function, so in the function application (const (raise ...)), (raise ...) is evaluated, causing the exception right away.

To control the evaluation order so that (raise ...) is delayed until called, you could either write an explicit lambda:

(mock #:behavior (lambda (_dummy) (raise ...)))

or use the macro thunk*.

(mock #:behavior (thunk* (raise ...)))

6 Likes

Ah, that makes sense. It had not occurred to me that const would be immediately evaluated. But yes thunk* works the way I need it to.

Thanks for your help.