Previously (here, here and here) we looked at users.py and how we can create a set of tests using mocks to validate it’s working as intended. The subject we were mocking in those previous examples was file_utils.py. Here’s what file_utils.py looks like;
|def write_file(path, content):|
|file_to_write = open(path, "w")|
|with open(path, 'r') as file_to_read:|
|data = file_to_read.read()|
Very simple, one function writes to a file and the other one reads from it. Easy! Let’s write some unit tests for it!
Remember when it comes to testing we want to avoid;
- Testing beyond our function. The test for file_utils.py isn’t the right place to validate Python’s open.
- Creating actual artifcats, in this case files to read from or write to. That will make the tests slower and is just unnecessary.
So to help us, we can again use mock, it even comes with a specific function to help us in this case, mock_open. That link explains the benefits of using mock_open rather than trying to create your own and is definetly worth reading before continuining.
Here’s some tests for the file_utils.py;
|with mock.patch("__builtin__.open", mock.mock_open(read_data="MOCKED"), create=True) as mock_file:|
|result = file_utils.read_file("path")|
|assert result == "MOCKED"|
|with mock.patch("__builtin__.open", mock.mock_open(), create=True) as mock_file:|
|if __name__ == '__main__':|
Given the previous posts this should look pretty familiar, but let’s look at some of the subtle differences.
with mock.patch("__builtin__.open", mock.mock_open(read_data="MOCKED"), create=True) as mock_file:
To test the read, we mock the builtin open, and replace it with our mock_open version. The read_data or the output in this case we’ve change to be “MOCKED”, that’s how we can control the contents of our mocked file.
The write is a bit more involved. First we create our mock in a similar fashion;
with mock.patch("__builtin__.open", mock.mock_open(), create=True) as mock_file:
Then we specify a return_value for our mock, and assert that the mocked write function is called at least once.
The reason being that we didn’t bother specifying a mock object up front, so we got the default which doesn’t have a mock for write.
That’s pretty much it! Thanks again for reading, as you can see mock once again provides useful features to help us easily control how our tests run. In the next posts we’ll be looking at how we can have our mocks throw exceptions, and then consider the implications of writing good unit tests.