b2spirit
b2spirit

Reputation: 3

What is the correct way to unit test a function or method that writes to an encrypted wallet file?

My code looks somewhat like this:

def write_wallet_file_entry(name, value, wallet_file, wallet_password):
    some_code
    some_more_code_to_write_to_the_wallet
    ...

I am using Python (2.6) and using unittest module for unit testing this code. Code creates the wallet file, if it does not exist and then writes a bunch of key value pairs to it.

Once I write to the wallet, there is no text parsing I can do to confirm that the write was clean.

CLARIFICATION: Stating the NOT SO obvious: I cannot use "unittest.mock" or "mock" module which would have made the problem simpler to solve. My environment is stuck on python 2.6, does not have "virtualenv", does not have "mock" module and does not allow installation of external modules on the system.

Any suggestions would be really helpful.

Upvotes: 0

Views: 233

Answers (1)

Akshat Mahajan
Akshat Mahajan

Reputation: 9846

Some Assumptions

These assumptions won't change the gist of my answer, but they will mean we can be clear about terminology, since you haven't posted a Minimum, Complete and Verifiable Example.

  • A 'wallet file' is literally a file-like object. It obeys the same semantics as a file stream object, for which Python's open() is a direct wrapper.

  • Only the wallet_file and wallet_password are wallet file-specific. name and value are a key-value pair you seek to pass in to the file.

The Issue

Your problem here is in being able to test that your writes are 'clean'.

However, you don't need to check if the file was written to correctly or if it was created - you'd only be testing Python's file object that way, which is pretty robustly tested already.

The point of unit tests is to test code you've written, not external services. It should always be assumed that the external service did its job in unit tests - you only test the external service in an integration test.

What you need is a way to ensure that the values you sent to be written were correctly received and not garbled, and that your request to create a file was received in the format you wanted. Test the message, not the recipient.

An Approach

One technique is to abstract your input as a class, and subclass it to have dummy methods. You can then use the subclass as a glorified mock, for all intents and purposes.

In other words, change

def write_wallet_file_entry(name, value, wallet_file, wallet_password):
    ...

to

class Wallet(object):

    def __init__(self, wallet_file, wallet_password):
        self.file = wallet_file
        self.password = wallet_password

    def exists(self):            
        # code to check if file exists in filesystem

    def write(self, name, value):
        # code to write name, value

def write_wallet_file_entry(name, value, WalletObject):
    assert isinstance(WalletObject, Wallet), "Only Wallets allowed" 
    if WalletObject.exists():
        WalletObject.write(name, value)

To test, you can now create MockWallet:

class MockWallet(Wallet):

    def __init__(self, wallet_file, wallet_password):
        super(MockWallet, self).__init__(wallet, wallet_password)
        self.didCheckExists = False
        self.didAttemptWrite = False
        self.didReceiveCorrectly = False

    def exists(self):
        super(MockWallet, self).exists()
        self.didCheckExists = True

    def write(self, name, value):
        # ... some code to validate correct arguments were pass
        self.didReceiveCorrectly = True
        if super(MockWallet, self).write(name, value):
            self.didAttemptWrite = True

Now you can use the same function in production (just pass a Wallet!) and in tests (just pass a MockWallet and check against attributes of that object!):

import unittest
from ... import MockWallet, write_wallet_file_entry

class Test(unittest.TestCase):

    def testWriteFlow(self):
        mock = MockWallet()
        name, value = 'random', 'more random'
        write_wallet_file_entry(name, value, mock)

        self.assertTrue(mock.didCheckExists)
        self.assertTrue(mock.didAttemptWrite)
        self.assertTrue(mock.didReceiveCorrectly)

Voila! You now have a tested write flow with a handily-improvised mock using nothing more than dependency injection instead of arbitrary function parameters.

Upvotes: 1

Related Questions