In part 1, I went over the module structure of a simple module, and a summary of the unit tests I would execute. In this post, I cover the actual nose tests.

1. Test module arguments

import mock
import library.prefixcheck as prefixcheck
from nose.tools import assert_equals

@mock.patch('library.prefixcheck.checkifrouteexists') @mock.patch('library.prefixcheck.AnsibleModule') def testmoduleargs(mockmodule, mockroutecheck): """ prefixcheck - test module arguments """ prefixcheck.main() mockmodule.assertcalledwith( argumentspec={ 'prefix': {'required': True, 'type': 'str'}, 'timeout': {'type': 'int', 'default': 5}, })

Mocking is important to unit testing Ansible modules. It took me a while and several videos to understand how Python Mock works. Do not pull your hair out if you do not get it the first time around.

mock.patch has a quirk that took a while to get used to. Notice that check_if_route_exists and AnsibleModule is mocked in that order. But in the test_module_args call, variables holding those mocks are in reverse order. the order is reversed, that is mock_modules first, followed by mock_route_check..Weird!

2. Test Exit functionality

@mock.patch('library.prefixcheck.checkifrouteexists')
@mock.patch('library.prefixcheck.AnsibleModule')
def testmainexitfunctionalitysuccess(mockmodule,
                     mockroutecheck):
    """
    prefixcheck - testmainexitfunctionality - success
    """

# This creates a instance of the AnsibleModule mock.
instance = mock_module.return_value

# What happens the route is found. Check that correct
# exit function is called
mock_route_check.return_value = True

prefix_check.main()

# AnsibleModule.exit_json should be called
# "changed" var should be false
instance.exit_json.assert_called_with(
        msg='Route Found', changed=False)

# AnsibleModule.fail_json should not be called
assert_equals(instance.fail_json.call_count, 0)

@mock.patch('library.prefixcheck.checkifrouteexists') @mock.patch('library.prefixcheck.AnsibleModule') def testmainexitfunctionalityfailure(mockmodule, mocklooproutecheck): """ prefixcheck - testmainexitfunctionality - failure """ instance = mockmodule.return_value

# What happens when the check_if_route_exists returns False
# that is route is not found
mock_loop_route_check.return_value = False

prefix_check.main()

# AnsibleModule.exit_json should be activated
assert_equals(instance.exit_json.call_count, 0)

# AnsibleModule.fail_json should be called
instance.fail_json.assert_called_with(
    msg='Route not Found. Check Routing Configuration')

In these tests, the "check route found" mocked function has a return value. This needs to be set before the prefix_check.main() function is called.

I broke out the success and failure test case into 2 separate functions, just to make it clear what I'm doing. Normally, I'm lazy and just keep my test cases in the same function and use reset_mock() to reinitialize the mocks before each use case. Probably bad practise though :)

Test route check function

@mock.patch('library.prefixcheck.singleroutecheckrun')
@mock.patch('library.prefixcheck.AnsibleModule')
def testroutecheckroutefound(mockmodule, mocksinglerun):
    """
    prefix_check - test action when prefix is found
    """

# on success, single_route_check_run() will return true
mock_single_run.return_value = True
result = prefix_check.check_if_route_exists(mock_module)
assert_equals(result, True)

@mock.patch('library.prefixcheck.singleroutecheckrun') @mock.patch('library.prefixcheck.AnsibleModule') def testroutecheckroutefound(mockmodule, mocksinglerun): """ prefix_check - test action when prefix is found """

# on success, single_route_check_run() will return true
mock_single_run.return_value = True
result = prefix_check.check_if_route_exists(mock_module)
assert_equals(result, True)

@mock.patch('library.prefixcheck.time.sleep') @mock.patch('library.prefixcheck.singleroutecheckrun') @mock.patch('library.prefixcheck.AnsibleModule') def testroutecheckroutetimeoutoccurs(mockmodule, mocksinglerun, mocksleep): """ prefixcheck - test action when prefix not found. timeout occurs """ # define some important variables for the test. mockmodule.timeout = 5 pollinterval = 5

# on failure, single_route_check_run() will return false
mock_single_run.return_value = False

# Run the function under test
result = prefix_check.check_if_route_exists(mock_module)

# Confirm that function failed
assert_equals(result, False)

# Also check that the poll interval works correctly.
# Mock time.sleep and check that it was executed
# (poll_interface/timeout) times. In this case 5 times.
assert_equals(mock_sleep.call_count, poll_interval)

@mock.patch('library.prefixcheck.AnsibleModule') def testiprouteshowexecution(mockmodule): """ prefix_check - test ip route show execution """

# define necessary variables. Prefix was initial
# set in the main() function. Needs to be hardcoded in this test.
mock_module.prefix = '10.1.1.0/24'

# AnsibleModule.run_command outputs list of output. Make
# sure to mimic this for the test.
mock_module.run_command.return_value = (1,'ip route stuff', None)

# Run the function under test
prefix_check.single_route_check_run(mock_module)

#Confirm command outputs used to get prefix info
mock_module.run_command.assert_called_with('/sbin/ip route show 10.1.1.0/24')

The hardest trick in these tests was mocking the time.sleep function. Wanted to be sure that it polls ip route show correctly.

The final test, ensures that future modifications don't mess up the ip route show call.

In part 3, I show how I run the tests.