Saturday, May 29, 2010

Python mock testing with pymock module

Python module pymock is based on EasyMock. Install pymock module:
easy_install pymock
Suppose you need to test withdraw operation in ATM (file atm.py):
from datetime import datetime

class Atm:
    def signin(self, account):
        self._account = account

    def signout(self):
        self._account = None

    def withdraw(self, amount):
        try:
            self._account.withdraw(amount)
            self._account.comission(amount * 0.005)
        except ValueError:
            self._account.comission(amount * 0.001)
        return self._account.balance(datetime.now())
Pymock uses a recording and replay model. Here is our test (file pymockexample.py):
from datetime import datetime
from atm import Atm
import unittest
from pymock import Controller, Any

class TestAtm(unittest.TestCase):

    def setUp(self):
        self._mocker = Controller()
        self._mock_account = self._mocker.mock()
        self._atm = Atm()
        self._atm.signin(self._mock_account)

    def tearDown(self):
        self._atm.signout()
        self._mocker.verify()

    def test_withdraw(self):
        # Arrange
        self._mock_account.deposit(150)
        self._mock_account.withdraw(100)
        self._mock_account.comission(0.5)

        # WARNING: This doesn't work
        # self._mock_account.balance(Any())
        self._mock_account.balance(datetime.now())
        self._mocker.returns(49.5)
        self._mocker.replay()

        # Act
        self._mock_account.deposit(150)
        remaining_balance = self._atm.withdraw(100)

        # Assert
        assert remaining_balance == 49.5

    def test_withdraw_insufficient_funds(self):
        # Arrange
        self._mock_account.deposit(50)
        self._mock_account.withdraw(100)
        self._mocker.raises(ValueError('Insufficient funds'))
        self._mock_account.comission(0.1)
        self._mock_account.balance(datetime.now())
        self._mocker.returns(49.9)
        self._mocker.replay()

        # Act
        self._mock_account.deposit(50)
        remaining_balance = self._atm.withdraw(100)

        # Assert
        assert remaining_balance == 49.9

if __name__ == '__main__':
    unittest.main()
Run tests:
python pymockexample.py
Read more about pymock here.

Python mock testing with mox module

Python module mox is based on EasyMock. Install mox module:
easy_install mox
Suppose you need to test withdraw operation in ATM (file atm.py):
from datetime import datetime

class Atm:
    def signin(self, account):
        self._account = account

    def signout(self):
        self._account = None

    def withdraw(self, amount):
        try:
            self._account.withdraw(amount)
            self._account.comission(amount * 0.005)
        except ValueError:
            self._account.comission(amount * 0.001)
        return self._account.balance(datetime.now())
When you create a mock object, it is in record mode. You record the behavior by calling the expected methods. Once you are done, switch to replay mode. Here is our test (file moxexample.py):
from datetime import datetime
from atm import Atm
import unittest
from mox import Mox, IgnoreArg, Func

class TestAtm(unittest.TestCase):

    def setUp(self):
        self._mox = Mox()
        self._mock_account = self._mox.CreateMockAnything()
        self._atm = Atm()
        self._atm.signin(self._mock_account)

    def tearDown(self):
        self._atm.signout()
        self._mox.VerifyAll()

    def test_withdraw(self):
        # Arrange
        self._mock_account.deposit(150)
        self._mock_account.withdraw(100)
        self._mock_account.comission(0.5)
        self._mock_account.balance(\
                Func(lambda d: d <= datetime.now()))\
                .AndReturn(49.5)
        self._mox.ReplayAll()

        # Act
        self._mock_account.deposit(150)
        remaining_balance = self._atm.withdraw(100)

        # Assert
        assert remaining_balance == 49.5

    def test_withdraw_insufficient_funds(self):
        # Arrange
        self._mock_account.deposit(50)
        self._mock_account.withdraw(100)\
                .AndRaise(ValueError('Insufficient funds'))
        self._mock_account.comission(0.1)
        self._mock_account.balance(IgnoreArg())\
                .AndReturn(49.9)
        self._mox.ReplayAll()

        # Act
        self._mock_account.deposit(50)
        remaining_balance = self._atm.withdraw(100)

        # Assert
        assert remaining_balance == 49.9

if __name__ == '__main__':
    unittest.main()
Run tests:
python moxexample.py
Read more about mox here.

Python mock testing with mock module

Install mock module:
easy_install mock
Suppose you need to test withdraw operation in ATM (file atm.py):
from datetime import datetime

class Atm:
    def signin(self, account):
        self._account = account

    def signout(self):
        self._account = None

    def withdraw(self, amount):
        try:
            self._account.withdraw(amount)
            self._account.comission(amount * 0.005)
        except ValueError:
            self._account.comission(amount * 0.001)
        return self._account.balance(datetime.now())
After performing an action on Mock instance, you can make assertions about which methods / attributes were used and arguments they were called with. Here is our test (file mockexample.py):
from datetime import datetime
from atm import Atm
import unittest
from mock import Mock

class TestAtm(unittest.TestCase):

    def setUp(self):
        self._mock_account = Mock()
        self._atm = Atm()
        self._atm.signin(self._mock_account)

    def tearDown(self):
        self._atm.signout()

    def test_withdraw(self):
        # Arrange
        def balance(d):
            self.assertTrue(d >= datetime.now()) 
            return 49.5
        self._mock_account.balance.side_effect = balance

        # Act
        self._mock_account.deposit(150)
        remaining_balance = self._atm.withdraw(100)

        # Assert
        assert remaining_balance == 49.5
        self._mock_account.deposit.assert_called_with(150)
        self._mock_account.withdraw.assert_called_with(100)
        self._mock_account.comission.assert_called_with(0.5)

    def test_withdraw_insufficient_funds(self):
        # Arrange
        self._mock_account.withdraw\
                .side_effect = ValueError('Insufficient funds')
        self._mock_account.balance.return_value = 49.9

        # Act
        self._mock_account.deposit(50)
        remaining_balance = self._atm.withdraw(100)

        # Assert
        assert remaining_balance == 49.9
        self._mock_account.deposit.assert_called_with(50)
        self._mock_account.withdraw.assert_called_with(100)
        self._mock_account.comission.assert_called_with(0.1)

if __name__ == '__main__':
    unittest.main()
Run tests:
python mockexample.py
Read more about mock here.

Python mock testing with mocker module

First of all install mocker:
easy_install mocker
Suppose you need to test withdraw operation in ATM (file atm.py):
from datetime import datetime

class Atm:
    def signin(self, account):
        self._account = account

    def signout(self):
        self._account = None

    def withdraw(self, amount):
        try:
            self._account.withdraw(amount)
            self._account.comission(amount * 0.005)
        except ValueError:
            self._account.comission(amount * 0.001)
        return self._account.balance(datetime.now())
A Mocker instance is used for expectations record/replay. Here is our test (file mockerexample.py):
import unittest
from datetime import datetime

from mocker import Mocker, ANY, expect
from atm import Atm


class TestAtm(unittest.TestCase):

    def setUp(self):
        self._mocker = Mocker()
        self._mock_account = self._mocker.mock()
        self._atm = Atm()
        self._atm.signin(self._mock_account)

    def tearDown(self):
        self._atm.signout()
        self._mocker.restore()
        self._mocker.verify()

    def test_withdraw(self):
        # Arrange
        self._mock_account.deposit(150)
        self._mock_account.withdraw(100)
        self._mock_account.comission(0.5)
        expect(self._mock_account.balance(ANY))\
                .result(49.5)\
                .call(lambda d: self.assertTrue(d <= datetime.now()))
        self._mocker.replay()

        # Act
        self._mock_account.deposit(150)
        remaining_balance = self._atm.withdraw(100)

        # Assert
        assert remaining_balance == 49.5

    def test_withdraw_insufficient_funds(self):
        # Arrange
        self._mock_account.deposit(50)
        expect(self._mock_account.withdraw(100))\
                .throw(ValueError('Insufficient funds'))
        self._mock_account.comission(0.1)
        expect(self._mock_account.balance(ANY))\
                .result(49.9)
        self._mocker.replay()

        # Act
        self._mock_account.deposit(50)
        remaining_balance = self._atm.withdraw(100)

        # Assert
        assert remaining_balance == 49.9

if __name__ == '__main__':
    unittest.main()
Run tests:
python mockerexample.py
Read more about mocker here.

Python mock testing with mockito module

This module is a port of the Mockito mocking framework to Python. Install mockito with easy_install:
easy_install mockito
Suppose you need to test withdraw operation in ATM (file atm.py):
from datetime import datetime

class Atm:
    def signin(self, account):
        self._account = account

    def signout(self):
        self._account = None

    def withdraw(self, amount):
        try:
            self._account.withdraw(amount)
            self._account.comission(amount * 0.005)
        except ValueError:
            self._account.comission(amount * 0.001)
        return self._account.balance(datetime.now())
Here is our test (file mockitoexample.py):
from datetime import datetime
from atm import Atm
import unittest
from mockito import Mock, when, verify, any, verifyNoMoreInteractions

class TestAtm(unittest.TestCase):

    def setUp(self):
        self._mock_account = Mock()
        self._atm = Atm()
        self._atm.signin(self._mock_account)

    def tearDown(self):
        self._atm.signout()
        verifyNoMoreInteractions(self._mock_account)

    def test_withdraw(self):
        # Arrange
        when(self._mock_account).balance(any(datetime))\
                .thenReturn(49.5)

        # Act
        self._mock_account.deposit(150)
        remaining_balance = self._atm.withdraw(100)

        # Assert
        assert remaining_balance == 49.5
        verify(self._mock_account).deposit(150)
        verify(self._mock_account).withdraw(100)
        verify(self._mock_account).comission(0.5)
        verify(self._mock_account).balance(any(datetime))

    def test_withdraw_insufficient_funds(self):
        # Arrange
        when(self._mock_account).withdraw(100)\
                .thenRaise(ValueError('Insufficient funds'))
        when(self._mock_account).balance(any(datetime))\
                .thenReturn(49.9)

        # Act
        self._mock_account.deposit(50)
        remaining_balance = self._atm.withdraw(100)

        # Assert
        assert remaining_balance == 49.9
        verify(self._mock_account).deposit(50)
        verify(self._mock_account).withdraw(100)
        verify(self._mock_account).comission(0.1)
        verify(self._mock_account).balance(any(datetime))

if __name__ == '__main__':
    unittest.main()
Run tests:
python mockitoexample.py
Read more about mockito here.