Pytest assertions for exit() or sys.exit()

I have a pretty standard function for my game call_quit() that accepts user input to confirm the quit, then either continues via a generic prompt() or calls exit() to kill the program.

Testing the strings associated with each if statement is easy, but how do you test that exit() has been called?

I’ve been reading this article which states a base exception of SystemExit will be thrown, and I am seeing this in the Pytest output error, but I cannot hook into it for the assertion.

Has anyone found a reliable way to do this?

def call_quit():                                                                                                                                       
    """Enables the player to confirm they want to quit the game."""                                                                                     
    confirm = input("Are you sure? (y/n) ")                                                                                                    
    if confirm == 'n':                                                                                                                               
        print("Game on...")                                                                                                                            
   elif confirm == 'y':                                                                                                                                
       print("See you soon...")                                                                                                                      
       print("I don't understand that...")                                                                                                            
import pytest
import mock

def test_call_quit_ends_the_game():                                       
    """Tests call_quit and confirming ends the session."""                
    with mock.patch('builtins.input', return_value='y'):                  
       pytest_wrapped_exit = pytest.raises(SystemExit)                   
       assert src.util.call_quit() == pytest_wrapped_exit.type 

I usually put the patch on the method and then work with it that way:

def test_mystuff(the_thing):
    assert the_thing.called

In your example I think you’re missing with/as:

with mock.patch('builtins.input', return_value='y') as input:
    assert input.called

Try that.

My approach is messy as I need to deal with the user input via the mock first. That seems to work fine. But I can’t work out how to assert that calling exit() or sys.exit() has actually ‘exited’. This seems like a useful trick to know.

That’s going to be rough. Honestly, if you’re trying to drive the game so you can test it then mock is not what you want to do. That’d work for some quick tests, but really it’d take you way too long to figure out. The easier way is to script it with this:

Your test suite that runs the game actually launches it in a subprocess that you use to capture the input and output. You can then read some, send some, read some, send some, etc. until you’ve done it.

Your only other option is to redesign the game so that the input is easily scriptable. In fact, the RefactorSchool S2-S5 I did this very thing with Didier’s code. You should just watch that whole series again, 'cause I attempted a few options in there, I think for S4 and S5.

Otherwise, don’t beat your head on this too long. You could get into a quagmire of trying to test this and it’ll drag you down. Just getting mock to work a bit is more than good enough for the book.