unittest.mock --- 入門

Added in version 3.3.

Mock を使う

モックでメソッドにパッチを当てる

一般的な Mock の使い方の中には次のものがあります:

  • メソッドにパッチを当てる

  • オブジェクトに対するメソッド呼び出しを記録する

システムの他の部分からメソッドが正しい引数で呼び出されたかどうかを確認するために、そのオブジェクトのメソッドを置き換えることができます:

>>> real = SomeClass()
>>> real.method = MagicMock(name='method')
>>> real.method(3, 4, 5, key='value')
<MagicMock name='method()' id='...'>

モック(上の例では real.method)が利用された場合、どう使われたかを assert できるようにする属性やメソッドがモックにあります。

注釈

この例のような場合、たいてい Mock と MagicMock は互換です。 MagicMock の方が強力なので、デフォルトではこちらを使うといいでしょう。

モックが呼び出されると、その called 属性が True に設定されます。そして assert_called_with() や assert_called_once_with() メソッドを使ってそのメソッドが正しい引数で呼び出されたかどうかをチェックできます。

次の例では ProductionClass().method が something メソッドを呼び出したことをテストしています:

>>> class ProductionClass:
...     def method(self):
...         self.something(1, 2, 3)
...     def something(self, a, b, c):
...         pass
...
>>> real = ProductionClass()
>>> real.something = MagicMock()
>>> real.method()
>>> real.something.assert_called_once_with(1, 2, 3)

オブジェクトのメソッド呼び出しにモックを使う

上の例ではオブジェクトのメソッドに対して直接パッチを当てて、それが正しく呼び出されていたかどうかをテストしていました。もう一つのよくあるユースケースが、モックをメソッド (またはテスト対象のシステムのどこか) に渡して、それが正しく利用されたかどうかをチェックする方法です。

次の例で、ProductionClass は closer メソッドを持っています。このメソッドは渡されたオブジェクトの close メソッドを呼び出します。

>>> class ProductionClass:
...     def closer(self, something):
...         something.close()
...

ですからこれをテストするには、close メソッドを持ったオブジェクトを渡して、それが正しく呼び出されたかどうかをテストしなければなりません。

>>> real = ProductionClass()
>>> mock = Mock()
>>> real.closer(mock)
>>> mock.close.assert_called_with()

モックに close メソッドを持たせるために何か準備する必要はありません。 close メソッドにアクセスすると自動的にそれが作られます。なので、もし close が呼び出されなかったとしてもテスト時に生成されるのですが、 assert_called_with() が failure 例外を発生させます。

クラスをモックする

他のよくあるユースケースが、テスト対象のコードによってインスタンス化されているクラスをモックに置き換えることです。クラスに patch すると、そのクラスがモックに置き換えられます。インスタンスは クラスを呼び出した時に 作られます。なので、モックの戻り値を使うことで、「モックのインスタンス」にアクセスできます。

次の例では、 some_function という関数が Foo をインスタンス化し、その method を呼び出しています。 patch() を呼び出すと Foo クラスをモックに置き換えます。 Foo のインスタンスはモックを呼び出して作られるので、モックの return_value を変更することでカスタマイズできます。:

>>> def some_function():
...     instance = module.Foo()
...     return instance.method()
...
>>> with patch('module.Foo') as mock:
...     instance = mock.return_value
...     instance.method.return_value = 'the result'
...     result = some_function()
...     assert result == 'the result'

モックに名前をつける

モックに名前をつけると便利なことがあります。その名前はモックを repr したときに表示されるので、モックがテスト失敗のメッセージ内に現れた時に便利です。また、モックの名前はそのモックの属性やメソッドにも伝播します:

>>> mock = MagicMock(name='foo')
>>> mock
<MagicMock name='foo' id='...'>
>>> mock.method
<MagicMock name='foo.method' id='...'>

すべての呼び出しを追跡する

メソッドの複数回の呼び出しをトラックしたいことがあります。 mock_calls 属性は、そのモックの子属性やさらにその子孫に対する呼び出しすべてを記録しています。

>>> mock = MagicMock()
>>> mock.method()
<MagicMock name='mock.method()' id='...'>
>>> mock.attribute.method(10, x=53)
<MagicMock name='mock.attribute.method()' id='...'>
>>> mock.mock_calls
[call.method(), call.attribute.method(10, x=53)]

mock_calls に対して assert すると、予期していないメソッド呼び出しがあったときにその assert が失敗します。これはあるメソッド呼び出しが期待通りに実行されたかどうかだけでなく、その呼び出し順序や期待した以外の呼び出しが起こらなかったことまでテストできるので便利です:

mock_calls と比較するリストを作るために call オブジェクトを利用できます:

>>> expected = [call.method(), call.attribute.method(10, x=53)]
>>> mock.mock_calls == expected
True

ただし、モックを返す呼び出しの引数は、その後の入れ子の呼び出しには記録されません。 そのため、祖先を作成した呼び出しの引数が重要な場合、その情報を含めて入れ子の呼び出しを追跡することはできません。

>>> m = Mock()
>>> m.factory(important=True).deliver()
<Mock name='mock.factory().deliver()' id='...'>
>>> m.mock_calls[-1] == call.factory(important=False).deliver()
True

戻り値と属性を設定する

モックオブジェクトに戻り値を設定するのはとっても簡単です:

>>> mock = Mock()
>>> mock.return_value = 3
>>> mock()
3

もちろん同じことがモックのメソッドに対しても行えます:

>>> mock = Mock()
>>> mock.method.return_value = 3
>>> mock.method()
3

コンストラクターで戻り値を設定することもできます:

>>> mock = Mock(return_value=3)
>>> mock()
3

モックに属性を設定したかったら、普通に設定するだけです:

>>> mock = Mock()
>>> mock.x = 3
>>> mock.x
3

mock.connection.cursor().execute("SELECT 1") のような複雑なケースでモックを使いたい場合もあります。この呼出があるリストを返すようにしたい場合、このネストした呼び出しを構成しなければなりません。

call を使って "chained call" 内の呼び出しを構成して、 assert で使うことができます:

>>> mock = Mock()
>>> cursor = mock.connection.cursor.return_value
>>> cursor.execute.return_value = ['foo']
>>> mock.connection.cursor().execute("SELECT 1")
['foo']
>>> expected = call.connection.cursor().execute("SELECT 1").call_list()
>>> mock.mock_calls
[call.connection.cursor(), call.connection.cursor().execute('SELECT 1')]
>>> mock.mock_calls == expected
True

call object を chained call を表す list にするために .call_list() を使います。

モックから例外を発生させる

side_effect という便利な属性があります。この属性に例外クラスやそのインスタンスを設定すると、モックが呼ばれた時にその例外を発生させます。

>>> mock = Mock(side_effect=Exception('Boom!'))
>>> mock()
Traceback (most recent call last):
  ...
Exception: Boom!

side_effect の関数と iterable

side_effect には関数や iterable を設定することもできます。side_effect に iterable を設定するユースケースは、そのモックが複数回呼び出され、そのたびに違う値を返したい場合です。side_effect に iterable を設定すると、そのモックに対するすべての呼び出しは iterable の次の値を返します:

>>> mock = MagicMock(side_effect=[4, 5, 6])
>>> mock()
4
>>> mock()
5
>>> mock()
6

より高度なユースケースとして、mock が呼び出された時の引数によって戻り値を変化させたい場合は、side_effect に関数を設定することができます。その関数は mock と同じ引数で呼び出されます。その関数の戻り値がそのモック呼び出しの戻り値になります:

>>> vals = {(1, 2): 1, (2, 3): 2}
>>> def side_effect(*args):
...     return vals[args]
...
>>> mock = MagicMock(side_effect=side_effect)
>>> mock(1, 2)
1
>>> mock(2, 3)
2

非同期イテレータをモックする

Python 3.8以降、AsyncMock と MagicMock は __aiter__ を通じて 非同期イテレータ (Asynchronous Iterator) のモックをサポートしています。__aiter__ の return_value 属性はイテレーションに使用される戻り値を設定するために使用できます。

>>> mock = MagicMock()  # AsyncMock also works here
>>> mock.__aiter__.return_value = [1, 2, 3]
>>> async def main():
...     return [i async for i in mock]
...
>>> asyncio.run(main())
[1, 2, 3]

非同期コンテキストマネージャをモックする

Python 3.8以降、AsyncMock と MagicMock は __aenter__ と __aexit__ を通じて 非同期コンテキストマネージャ (Asynchronous Context Manager) のモックをサポートしています。デフォルトでは、 __aenter__ と __aexit__ は非同期関数を返す AsyncMock インスタンスです。

>>> class AsyncContextManager:
...     async def __aenter__(self):
...         return self
...     async def __aexit__(self, exc_type, exc, tb):
...         pass
...
>>> mock_instance = MagicMock(AsyncContextManager())  # AsyncMock also works here
>>> async def main():
...     async with mock_instance as result:
...         pass
...
>>> asyncio.run(main())
>>> mock_instance.__aenter__.assert_awaited_once()
>>> mock_instance.__aexit__.assert_awaited_once()

既存のオブジェクトからモックを作る

mock を使いすぎることの問題の一つは、テストが実際のコードではなく mock の実装をテストするようになってしまうことです。some_method というメソッドを実装したクラスがあるとします。他のクラスをテストするときに、some_method を提供する mock を使います。最初のクラスをリファクタリングして some_method がなくなった時、コードは壊れているのにテストは通る状態になってしまいます

Mock は spec というキーワード引数で mock の定義となるオブジェクトを指定できます。定義オブジェクトに存在しないメソッドや属性にアクセスすると AttributeError を発生させます。定義となるクラスの実装を変更した場合、テストの中でそのクラスをインスタンス化させなくても、テストを失敗させる事ができます。

>>> mock = Mock(spec=SomeClass)
>>> mock.old_method()
Traceback (most recent call last):
   ...
AttributeError: Mock object has no attribute 'old_method'. Did you mean: 'class_method'?

spec を指定すると、モックへの呼び出しをより柔軟に照合でき、引数が位置引数として渡されたか、名前を指定して渡されたかを問いません。

>>> def f(a, b, c): pass
...
>>> mock = Mock(spec=f)
>>> mock(1, 2, 3)
<Mock name='mock()' id='140161580456576'>
>>> mock.assert_called_with(a=1, b=2, c=3)

このよりスマートなマッチングをモックのメソッド呼び出しにも適用したい場合は、auto-speccing を使用します。

任意の属性の参照だけでなく代入も禁止するより強い定義を利用したい場合は、spec の代わりに spec_set を使います。

side_effect でファイルごとに異なる内容を返す

mock_open() は open() にパッチを適用するために使います。 side_effect を使うと、呼び出しのたびに新しい Mock オブジェクトを返せます。 これを利用して、辞書に保存した異なる内容をファイルごとに返せます。

DEFAULT = "default"
data_dict = {"file1": "data1",
             "file2": "data2"}

def open_side_effect(name):
    return mock_open(read_data=data_dict.get(name, DEFAULT))()

with patch("builtins.open", side_effect=open_side_effect):
    with open("file1") as file1:
        assert file1.read() == "data1"

    with open("file2") as file2:
        assert file2.read() == "data2"

    with open("file3") as file2:
        assert file2.read() == "default"

patch デコレータ

注釈

patch() では探索される名前空間内のオブジェクトにパッチをあてることが重要です。通常は単純ですが、クイックガイドには どこにパッチするか を読んでください。

テストの中でクラス属性やモジュール属性、例えば組み込み関数や、テスト対象モジュールにあるインスタンス化されるクラスに対してパッチしたいことがあります。モジュールやクラスは実際はグローバルなので、パッチするときは必ずテスト後にパッチを解除しないと、そのパッチが永続化されて他のテストに影響を与え、解析しにくい問題になります。

mock はこのために3つの便利なデコレータを提供しています: patch(), patch.object(), patch.dict() です。patch はパッチ対象を指定する package.module.Class.attribute の形式の文字列を引数に取ります。オプションでその属性 (やクラスなど) を置き換えるオブジェクトを渡すことができます。 'patch.object' はオブジェクトとパッチしたい属性名、それにオプションで置き換えるオブジェクトを受け取ります。

patch.object:

>>> original = SomeClass.attribute
>>> @patch.object(SomeClass, 'attribute', sentinel.attribute)
... def test():
...     assert SomeClass.attribute == sentinel.attribute
...
>>> test()
>>> assert SomeClass.attribute == original

>>> @patch('package.module.attribute', sentinel.attribute)
... def test():
...     from package.module import attribute
...     assert attribute is sentinel.attribute
...
>>> test()

モジュール (builtins を含む) をパッチしようとする場合、 patch.object() の代わりに patch() を使用してください:

>>> mock = MagicMock(return_value=sentinel.file_handle)
>>> with patch('builtins.open', mock):
...     handle = open('filename', 'r')
...
>>> mock.assert_called_with('filename', 'r')
>>> assert handle == sentinel.file_handle, "incorrect file handle returned"

モジュール名は必要に応じて package.module のようにドットを含むことができます:

>>> @patch('package.module.ClassName.attribute', sentinel.attribute)
... def test():
...     from package.module import ClassName
...     assert ClassName.attribute == sentinel.attribute
...
>>> test()

テストメソッド自体をデコレートするのは良いパターンです:

>>> class MyTest(unittest.TestCase):
...     @patch.object(SomeClass, 'attribute', sentinel.attribute)
...     def test_something(self):
...         self.assertEqual(SomeClass.attribute, sentinel.attribute)
...
>>> original = SomeClass.attribute
>>> MyTest('test_something').test_something()
>>> assert SomeClass.attribute == original

Mock を使ってパッチしたい場合は、 patch() を1引数で (または patch.object() を2引数で) 使うことができます。mock が自動で生成され、テスト関数/メソッドに渡されます:

>>> class MyTest(unittest.TestCase):
...     @patch.object(SomeClass, 'static_method')
...     def test_something(self, mock_method):
...         SomeClass.static_method()
...         mock_method.assert_called_with()
...
>>> MyTest('test_something').test_something()

次のパターンのように patch デコレータを重ねることができます:

>>> class MyTest(unittest.TestCase):
...     @patch('package.module.ClassName1')
...     @patch('package.module.ClassName2')
...     def test_something(self, MockClass2, MockClass1):
...         self.assertIs(package.module.ClassName1, MockClass1)
...         self.assertIs(package.module.ClassName2, MockClass2)
...
>>> MyTest('test_something').test_something()

patch デコレータをネストした際、モックは (デコレータを適用する Python の通常の) 順に適用されます。つまり引数は下から上の順になり、よって上記の例では test_module.ClassName2 が先になります。

また、 patch.dict() を使うと、スコープ内だけで辞書に値を設定し、テスト終了時には元の状態に復元されます:

>>> foo = {'key': 'value'}
>>> original = foo.copy()
>>> with patch.dict(foo, {'newkey': 'newvalue'}, clear=True):
...     assert foo == {'newkey': 'newvalue'}
...
>>> assert foo == original

patch, patch.object, patch.dict は全てコンテキストマネージャーとしても利用できます。

patch() に mock を生成させる場合、その参照を with 文の as を使って受け取れます:

>>> class ProductionClass:
...     def method(self):
...         pass
...
>>> with patch.object(ProductionClass, 'method') as mock_method:
...     mock_method.return_value = None
...     real = ProductionClass()
...     real.method(1, 2, 3)
...
>>> mock_method.assert_called_with(1, 2, 3)

他の方法として、patch, patch.object, patch.dict はクラスデコレータとしても利用できます。その場合、"test" で始まる全てのメソッドにデコレータを適用するのと同じになります。

さらに詳しい例

より高度なシナリオを想定した例をあげていきます。

chained call をモックする

chained call を mock するのは、一度 return_value 属性を理解してしまえば簡単です。mock が最初に呼ばれた時や、呼び出される前に return_value を参照した場合、新しい Mock が生成されます。

つまり、戻り値のオブジェクトがどう利用されたかは、return_value mock を調べれば分かります:

>>> mock = Mock()
>>> mock().foo(a=2, b=3)
<Mock name='mock().foo()' id='...'>
>>> mock.return_value.foo.assert_called_with(a=2, b=3)

これをもとに、mock を構成して chained call に対する assert を行うのは簡単です。もちろん、元のコードを最初からもっとテストしやすく書くという選択肢もありますが...

では、例として次のようなコードがあるとします:

>>> class Something:
...     def __init__(self):
...         self.backend = BackendProvider()
...     def method(self):
...         response = self.backend.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
...         # more code

BackendProvider はすでに十分テストされているとします。method() をどうテストしましょうか?特に、response オブジェクトを使う # more code の部分のコードをテストしたいとします。

この chained call はインスタンス属性を起点にしているので、 Something インスタンスの backend 属性に対してモンキーパッチすることができます。今回の場合だと、最後の start_call の呼び出しが返す値にだけ興味があるので、あまり多くの構成は必要ありません。このメソッドが返すのが 'file-like' オブジェクトだとしましょう。そうすると、 response オブジェクトは組み込みの open() を spec として利用できます。

これをするために、backend のモックとしてモックインスタンスを作成し、それに対するモックの response オブジェクトを作成します。最終的な start_call の返り値として response をセットすると、このようにすることができます:

mock_backend.get_endpoint.return_value.create_call.return_value.start_call.return_value = mock_response

これより少し良いやり方として、返り値を直接セットする configure_mock() メソッドを使用して次のようにすることができます:

>>> something = Something()
>>> mock_response = Mock(spec=open)
>>> mock_backend = Mock()
>>> config = {'get_endpoint.return_value.create_call.return_value.start_call.return_value': mock_response}
>>> mock_backend.configure_mock(**config)

これらによって「モックの backend」をその場で monkey patch して、実際の呼び出しを行うことができます:

>>> something.backend = mock_backend
>>> something.method()

mock_calls を使用すると、チェーンされた呼び出しを単一のアサーションでチェックすることができます。チェーンされた呼び出しは、1行のコードの中で行われる複数の呼び出しです。したがって、 mock_calls には複数のエントリーがあるでしょう。 call.call_list() を使用することで、この呼び出しのリストを作成することができます:

>>> chained = call.get_endpoint('foobar').create_call('spam', 'eggs').start_call()
>>> call_list = chained.call_list()
>>> assert mock_backend.mock_calls == call_list

部分的なモック

テストによっては、datetime.date.today() をモックに置き換えて決まった日付を返すようにしつつ、 テスト対象のコードでは新しい日付オブジェクトを作成できるようにしたい場合があります。 しかし、datetime.date は C で実装されているため、静的メソッド datetime.date.today() を単純にモンキーパッチすることはできません。

代わりに、date クラスをモックでラップし、コンストラクタの呼び出しは本物のクラスに渡す (本物のインスタンスを返す) 方法が使えます。

ここで、テスト対象のモジュール中の date クラスをモックするために patch decorator が使用されています。そして、モックの date クラスの side_effect 属性は、本物の date を返すラムダ関数にセットされます。モックの date クラスが呼ばれる時、 side_effect によって本物の date が構築されて返されます。:

>>> import datetime as dt
>>> with patch('mymodule.date') as mock_date:
...     mock_date.today.return_value = dt.date(2010, 10, 8)
...     mock_date.side_effect = lambda *args, **kw: dt.date(*args, **kw)
...
...     assert mymodule.date.today() == dt.date(2010, 10, 8)
...     assert mymodule.date(2009, 6, 8) == dt.date(2009, 6, 8)

グローバルに datetime.date にパッチするのではなく、それを使用するモジュールの中の date にパッチしていることに留意してください。どこにパッチするか を参照してください。

When date.today() is called a known date is returned, but calls to the date(...) constructor still return normal dates. Without this you can find yourself having to calculate an expected result using exactly the same algorithm as the code under test, which is a classic testing anti-pattern.

dateコンストラクタの呼び出しは、mock_date の属性(call_count とその仲間)に記録され、テストに役立つこともあります。

日付や他の組み込みクラスのモックに対処する別の方法は このブログエントリ.で議論されています。

ジェネレータメソッドをモックする

Pythonのジェネレータは、反復処理された時に一連の値を返す yield 文を使用した関数やメソッドです [1]。

ジェネレータメソッド/関数はジェネレータオブジェクトを返すために呼び出されます。そしてこのジェネレータオブジェクトが反復処理されます。イテレーションのためのプロトコルメソッドは __iter__() なので、MagicMock を使ってこれをモックすることができます。

ここでは"iter"メソッドをジェネレータとして実装したクラスの例を紹介します:

>>> class Foo:
...     def iter(self):
...         for i in [1, 2, 3]:
...             yield i
...
>>> foo = Foo()
>>> list(foo.iter())
[1, 2, 3]

このクラス、特に "iter"メソッドをどのようにモックするのでしょうか。

(list の呼び出しの中での暗黙的な)イテレーションから返される値を設定するには、foo.iter() の呼び出しで返されるオブジェクトを設定する必要がある。

>>> mock_foo = MagicMock()
>>> mock_foo.iter.return_value = iter([1, 2, 3])
>>> list(mock_foo.iter())
[1, 2, 3]

同じパッチを全てのメソッドに適用する

複数のテストメソッドに複数のパッチを適用するには、各メソッドに patch デコレータを付ける方法が考えられますが、同じ記述を繰り返すことになります。 代わりに patch() (およびその各種の形式) をクラスデコレータとして使えます。 これにより、クラス内のすべてのテストメソッドにパッチを適用します。 名前が test で始まるメソッドをテストメソッドとみなします。

>>> @patch('mymodule.SomeClass')
... class MyTest(unittest.TestCase):
...
...     def test_one(self, MockSomeClass):
...         self.assertIs(mymodule.SomeClass, MockSomeClass)
...
...     def test_two(self, MockSomeClass):
...         self.assertIs(mymodule.SomeClass, MockSomeClass)
...
...     def not_a_test(self):
...         return 'something'
...
>>> MyTest('test_one').test_one()
>>> MyTest('test_two').test_two()
>>> MyTest('test_two').not_a_test()
'something'

パッチを管理する別の方法として patch のメソッド: start と stop も使えます。 これらを使うと、パッチを適用・解除する処理を setUp メソッドと tearDown メソッドに移せます。

>>> class MyTest(unittest.TestCase):
...     def setUp(self):
...         self.patcher = patch('mymodule.foo')
...         self.mock_foo = self.patcher.start()
...
...     def test_foo(self):
...         self.assertIs(mymodule.foo, self.mock_foo)
...
...     def tearDown(self):
...         self.patcher.stop()
...
>>> MyTest('test_foo').run()

この方式を使う場合、必ず stop メソッドを呼び出してパッチが解除する必要があります。setUp の中で例外が発生した場合 tearDown が呼び出されないので、これは意外に面倒です。 unittest.TestCase.addCleanup() を使うと簡単にできます:

>>> class MyTest(unittest.TestCase):
...     def setUp(self):
...         patcher = patch('mymodule.foo')
...         self.addCleanup(patcher.stop)
...         self.mock_foo = patcher.start()
...
...     def test_foo(self):
...         self.assertIs(mymodule.foo, self.mock_foo)
...
>>> MyTest('test_foo').run()

束縛されていないメソッドをモックする

テストでは、束縛されていないメソッド にパッチを適用したいことがあります。 つまり、インスタンス上ではなくクラス上のメソッドにパッチを適用します。 どのオブジェクトがそのメソッドを呼び出したかをアサートするには、最初の引数として self を渡す必要があります。 ところが、束縛されていないメソッドをモックに置き換えると、インスタンスから取得しても束縛されたメソッドにならず、self が渡されません。 このため、モックでそのまま置き換えることはできません。 回避策は、本物の関数に置き換えることです。 patch() デコレータを使えばメソッドを簡単にモックに置き換えられるだけに、本物の関数を作成する手間は煩わしく感じられます。

patch に autospec=True を渡すと、本物の 関数オブジェクトで置き換えます。 この関数オブジェクトは置換対象と同じシグネチャを持ちますが、内部ではモックに処理を委譲します。 これまでと同じようにモックは自動的に作成されます。 ただし、クラス上の束縛されていないメソッドをこの方法で置き換えると、 置き換えた関数をインスタンスから取得したときに束縛されたメソッドになります。 そのため、必要としていたとおりに、最初の引数として self が渡されます。

>>> class Foo:
...   def foo(self):
...     pass
...
>>> with patch.object(Foo, 'foo', autospec=True) as mock_foo:
...   mock_foo.return_value = 'foo'
...   foo = Foo()
...   foo.foo()
...
'foo'
>>> mock_foo.assert_called_once_with(foo)

autospec=True を使用しない場合、束縛されていないメソッドは代わりにMockインスタンスでパッチされ、self で呼び出されることはありません。

モックで複数回の呼び出しをチェックする

モックには、モックオブジェクトがどのように使用されるかをアサートするための素晴らしいAPIがあります。

>>> mock = Mock()
>>> mock.foo_bar.return_value = None
>>> mock.foo_bar('baz', spam='eggs')
>>> mock.foo_bar.assert_called_with('baz', spam='eggs')

モックが一度だけ呼び出されるなら、assert_called_once_with() メソッドを使えます。 このメソッドは call_count が 1 であることもアサートします。

>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
>>> mock.foo_bar()
>>> mock.foo_bar.assert_called_once_with('baz', spam='eggs')
Traceback (most recent call last):
    ...
AssertionError: Expected 'foo_bar' to be called once. Called 2 times.
Calls: [call('baz', spam='eggs'), call()].

Both assert_called_with and assert_called_once_with make assertions about the most recent call. If your mock is going to be called several times, and you want to make assertions about all those calls you can use call_args_list:

>>> mock = Mock(return_value=None)
>>> mock(1, 2, 3)
>>> mock(4, 5, 6)
>>> mock()
>>> mock.call_args_list
[call(1, 2, 3), call(4, 5, 6), call()]

call ヘルパーを使えば、これらの呼び出しについて簡単にアサートすることができます。予想される呼び出しのリストを作成し、それを call_args_list と比較することができます。これは call_args_list のreprと驚くほど似ています:

>>> expected = [call(1, 2, 3), call(4, 5, 6), call()]
>>> mock.call_args_list == expected
True

ミュータブルな引数に対処する

稀ではありますが、問題となることがあるのはミュータブルな引数でモックが呼び出された場合です。call_args や call_args_list は引数への*参照*を保存します。もしテスト対象のコードによって引数が変更されてしまうと、モックが呼び出された時の値をアサートすることはできなくなります。

この問題を示すコード例を見てみましょう。 次の関数が「mymodule」に定義されているとします。

def frob(val):
    pass

def grob(val):
    "まず frob を呼び出し、その後 val を空にする。"
    frob(val)
    val.clear()

grob が frob を正しい引数で呼び出していることをテストしようとすると何が起こるか見てみましょう:

>>> with patch('mymodule.frob') as mock_frob:
...     val = {6}
...     mymodule.grob(val)
...
>>> val
set()
>>> mock_frob.assert_called_with({6})
Traceback (most recent call last):
    ...
AssertionError: Expected: (({6},), {})
Called with: ((set(),), {})

一つの選択肢は、あなたが渡す引数をモックがコピーすることです。この方法は、オブジェクトidの同一性に依存したアサーションを行う場合に問題を引き起こす可能性があります。

side_effect を使う解決策があります。 モックに side_effect 関数を指定すると、その関数はモックと同じ引数で呼び出されます。 そこで引数をコピーして保存し、後でアサートできます。 この例では、モックのメソッドでアサートできるように、別の モックで引数を保存します。 ここでもヘルパー関数を使って準備します。

>>> from copy import deepcopy
>>> from unittest.mock import Mock, patch, DEFAULT
>>> def copy_call_args(mock):
...     new_mock = Mock()
...     def side_effect(*args, **kwargs):
...         args = deepcopy(args)
...         kwargs = deepcopy(kwargs)
...         new_mock(*args, **kwargs)
...         return DEFAULT
...     mock.side_effect = side_effect
...     return new_mock
...
>>> with patch('mymodule.frob') as mock_frob:
...     new_mock = copy_call_args(mock_frob)
...     val = {6}
...     mymodule.grob(val)
...
>>> new_mock.assert_called_with({6})
>>> new_mock.call_args
call({6})

copy_call_args は、テストで呼び出されるモックとともに呼び出されます。この関数はアサーションで使用するための新しいモックを返します。side_effect 関数は引数のコピーを作成し、そのコピーを使って new_mock を呼び出します。

注釈

もしモックが一度しか使われないのであれば、呼び出された時点で引数をチェックする簡単な方法があります。単純に side_effect 関数の中でチェックを行えばいいのです。

>>> def side_effect(arg):
...     assert arg == {6}
...
>>> mock = Mock(side_effect=side_effect)
>>> mock({6})
>>> mock(set())
Traceback (most recent call last):
    ...
AssertionError

別の方法としては、( copy.deepcopy() を使用して)引数をコピーする、 Mock や MagicMock のサブクラスを作成する方法があります。以下に実装例を示します。

>>> from copy import deepcopy
>>> class CopyingMock(MagicMock):
...     def __call__(self, /, *args, **kwargs):
...         args = deepcopy(args)
...         kwargs = deepcopy(kwargs)
...         return super().__call__(*args, **kwargs)
...
>>> c = CopyingMock(return_value=None)
>>> arg = set()
>>> c(arg)
>>> arg.add(1)
>>> c.assert_called_with(set())
>>> c.assert_called_with(arg)
Traceback (most recent call last):
    ...
AssertionError: expected call not found.
Expected: mock({1})
Actual: mock(set())
>>> c.foo
<CopyingMock name='mock.foo' id='...'>

Mock または MagicMock をサブクラス化すると、動的に作成されるすべての属性と return_value に、自動的にそのサブクラスが使われます。 従って、CopyingMock の子モックもすべて CopyingMock 型になります。

パッチの入れ子

patch をコンテキストマネージャとして使うと便利ですが、複数のパッチを適用すると with 文が入れ子になり、インデントが次第に深くなります。

>>> class MyTest(unittest.TestCase):
...
...     def test_foo(self):
...         with patch('mymodule.Foo') as mock_foo:
...             with patch('mymodule.Bar') as mock_bar:
...                 with patch('mymodule.Spam') as mock_spam:
...                     assert mymodule.Foo is mock_foo
...                     assert mymodule.Bar is mock_bar
...                     assert mymodule.Spam is mock_spam
...
>>> original = mymodule.Foo
>>> MyTest('test_foo').test_foo()
>>> assert mymodule.Foo is original

unittest の cleanup 関数と patch のメソッド: start と stop を使うと、インデントを入れ子にせず同じことを実現できます。 単純なヘルパーメソッド create_patch で、パッチを適用して作成したモックを返します。

>>> class MyTest(unittest.TestCase):
...
...     def create_patch(self, name):
...         patcher = patch(name)
...         thing = patcher.start()
...         self.addCleanup(patcher.stop)
...         return thing
...
...     def test_foo(self):
...         mock_foo = self.create_patch('mymodule.Foo')
...         mock_bar = self.create_patch('mymodule.Bar')
...         mock_spam = self.create_patch('mymodule.Spam')
...
...         assert mymodule.Foo is mock_foo
...         assert mymodule.Bar is mock_bar
...         assert mymodule.Spam is mock_spam
...
>>> original = mymodule.Foo
>>> MyTest('test_foo').run()
>>> assert mymodule.Foo is original

MagicMock で辞書をモックする

辞書や他のコンテナをモックに置き換え、辞書としての振る舞いを保ちながら、すべてのアクセスを記録したい場合があります。

MagicMock を辞書のように振る舞わせ、side_effect で辞書へのアクセスを、自分で管理する本物の辞書に委譲すると実現できます。

MagicMock の __getitem__() メソッドや __setitem__() メソッドが呼び出されると (通常の辞書アクセス)、 side_effect にキーが渡されます (__setitem__ の場合は値も渡されます)。 戻り値も制御できます。

MagicMock を使った後、call_args_list などの属性で、辞書がどう使われたかをアサートできます。

>>> my_dict = {'a': 1, 'b': 2, 'c': 3}
>>> def getitem(name):
...      return my_dict[name]
...
>>> def setitem(name, val):
...     my_dict[name] = val
...
>>> mock = MagicMock()
>>> mock.__getitem__.side_effect = getitem
>>> mock.__setitem__.side_effect = setitem

注釈

MagicMock の代わりに Mock を使い、必要な特殊メソッド だけ を用意することもできます。

>>> mock = Mock()
>>> mock.__getitem__ = Mock(side_effect=getitem)
>>> mock.__setitem__ = Mock(side_effect=setitem)

3 つ目 の方法は、MagicMock の spec (または spec_set) 引数に dict を渡すことです。 これにより、作成した MagicMock で使える特殊メソッドを辞書のものだけに制限できます。

>>> mock = MagicMock(spec_set=dict)
>>> mock.__getitem__.side_effect = getitem
>>> mock.__setitem__.side_effect = setitem

これらの side_effect 関数を設定すると、mock は通常の辞書のように振る舞いながらアクセスを記録します。 存在しないキーにアクセスしようとすると、KeyError も送出します。

>>> mock['a']
1
>>> mock['c']
3
>>> mock['d']
Traceback (most recent call last):
    ...
KeyError: 'd'
>>> mock['b'] = 'fish'
>>> mock['d'] = 'eggs'
>>> mock['b']
'fish'
>>> mock['d']
'eggs'

使用後は、通常のモックのメソッドと属性を使ってアクセスをアサートできます。

>>> mock.__getitem__.call_args_list
[call('a'), call('c'), call('d'), call('b'), call('d')]
>>> mock.__setitem__.call_args_list
[call('b', 'fish'), call('d', 'eggs')]
>>> my_dict
{'a': 1, 'b': 'fish', 'c': 3, 'd': 'eggs'}

Mock のサブクラスと属性

Mock をサブクラス化したい理由はいろいろあります。 例えばヘルパーメソッドを追加するためです。 単純な例を見てみましょう。

>>> class MyMock(MagicMock):
...     def has_been_called(self):
...         return self.called
...
>>> mymock = MyMock(return_value=None)
>>> mymock
<MyMock id='...'>
>>> mymock.has_been_called()
False
>>> mymock()
>>> mymock.has_been_called()
True

Mock インスタンスの標準的な振る舞いでは、属性と戻り値のモックは、それらにアクセスした元のモックと同じ型になります。 このため、Mock の属性は Mock、MagicMock の属性は MagicMock になります [2]。 従って、サブクラス化してヘルパーメソッドを追加すると、 そのサブクラスのインスタンスの属性や戻り値のモックでもヘルパーメソッドを使えます。

>>> mymock.foo
<MyMock name='mock.foo' id='...'>
>>> mymock.foo.has_been_called()
False
>>> mymock.foo()
<MyMock name='mock.foo()' id='...'>
>>> mymock.foo.has_been_called()
True

これが不便な場合もあります。 例えば、ある利用者 は、モックをサブクラス化して Twisted アダプター を作成しています。 そのサブクラスが属性にも使われると、エラーが起きてしまいます。

Mock (およびその各種のクラス) は、属性や戻り値の「子モック」を作成するために _get_child_mock メソッドを使います。 このメソッドをオーバーライドすると、属性に自分のサブクラスが使われるのを防げます。 シグネチャは任意のキーワード引数 (**kwargs) を受け取り、それらをモックのコンストラクタに渡す形です。

>>> class Subclass(MagicMock):
...     def _get_child_mock(self, /, **kwargs):
...         return MagicMock(**kwargs)
...
>>> mymock = Subclass()
>>> mymock.foo
<MagicMock name='mock.foo' id='...'>
>>> assert isinstance(mymock, Subclass)
>>> assert not isinstance(mymock.foo, Subclass)
>>> assert not isinstance(mymock(), Subclass)

patch.dict で import をモックする

モックへの置き換えが難しくなる状況の 1 つが、関数内でローカルにインポートしている場合です。 パッチで置き換えられるモジュールの名前空間のオブジェクトを使っていないため、置き換えにくくなります。

一般に、ローカルなインポートは避ける方がよいでしょう。 循環依存を防ぐために使われることもありますが、通常は コードをリファクタリングするという、もっとよい解決策があります。 また、インポートを遅らせて「初期コスト」を避けるために使われることもあります。 これも無条件のローカルなインポートよりよい方法で解決できます (モジュールをクラスやモジュールの属性に保存し、最初に使うときだけインポートします)。

その点はさておき、mock を使ってインポートの結果を変える方法があります。 インポートは sys.modules 辞書から オブジェクト を取得します。 取得するのは オブジェクト であって、モジュールとは限りません。 モジュールを初めてインポートするとモジュールオブジェクトが sys.modules に格納されるため、通常はインポートするとモジュールが得られます。 しかし、必ずしもそうである必要はありません。

つまり、patch.dict() を使って sys.modules に 一時的に モックを配置できます。 パッチが有効な間にそのモジュールをインポートすると、モックが取得されます。 パッチが終了すると (デコレートした関数が終了する、with 文の本体が完了する、または patcher.stop() が呼ばれると)、以前の内容が安全に復元されます。

「fooble」モジュールをモックに置き換える例です。

>>> import sys
>>> mock = Mock()
>>> with patch.dict('sys.modules', {'fooble': mock}):
...    import fooble
...    fooble.blob()
...
<Mock name='mock.blob()' id='...'>
>>> assert 'fooble' not in sys.modules
>>> mock.blob.assert_called_once_with()

このように import fooble は成功しますが、終了時には sys.modules に「fooble」は残っていません。

from module import name の形式でも同じように動作します。

>>> mock = Mock()
>>> with patch.dict('sys.modules', {'fooble': mock}):
...    from fooble import blob
...    blob.blip()
...
<Mock name='mock.blob.blip()' id='...'>
>>> mock.blob.blip.assert_called_once_with()

少し手を加えると、パッケージのインポートもモックに置き換えられます。

>>> mock = Mock()
>>> modules = {'package': mock, 'package.module': mock.module}
>>> with patch.dict('sys.modules', modules):
...    from package.module import fooble
...    fooble()
...
<Mock name='mock.module.fooble()' id='...'>
>>> mock.module.fooble.assert_called_once_with()

呼び出し順序の追跡と簡潔な呼び出しのアサート

Mock クラスでは、method_calls 属性でモックオブジェクトのメソッドの呼び出し 順序 を追跡できます。 これでは別々のモックオブジェクト間の呼び出し順序を追跡できませんが、mock_calls を使うと同じことを実現できます。

モックは子モックへの呼び出しを mock_calls に記録し、任意の属性にアクセスすると子モックを作成します。 そこで、1 つの親モックから個々のモックを作成できます。 子モックへの呼び出しはすべて、親の mock_calls に順に記録されます。

>>> manager = Mock()
>>> mock_foo = manager.foo
>>> mock_bar = manager.bar
>>> mock_foo.something()
<Mock name='mock.foo.something()' id='...'>
>>> mock_bar.other.thing()
<Mock name='mock.bar.other.thing()' id='...'>
>>> manager.mock_calls
[call.foo.something(), call.bar.other.thing()]

管理用モックの mock_calls 属性と比較することで、順序を含めて呼び出しをアサートできます。

>>> expected_calls = [call.foo.something(), call.bar.other.thing()]
>>> manager.mock_calls == expected_calls
True

patch でモックを作成して配置する場合は、attach_mock() メソッドで管理用モックに取り付けられます。 取り付けた後の呼び出しは、管理用モックの mock_calls に記録されます。

>>> manager = MagicMock()
>>> with patch('mymodule.Class1') as MockClass1:
...     with patch('mymodule.Class2') as MockClass2:
...         manager.attach_mock(MockClass1, 'MockClass1')
...         manager.attach_mock(MockClass2, 'MockClass2')
...         MockClass1().foo()
...         MockClass2().bar()
<MagicMock name='mock.MockClass1().foo()' id='...'>
<MagicMock name='mock.MockClass2().bar()' id='...'>
>>> manager.mock_calls
[call.MockClass1(),
call.MockClass1().foo(),
call.MockClass2(),
call.MockClass2().bar()]

多くの呼び出しがあっても、そのうちの特定の一連の呼び出しだけを調べたい場合は、assert_has_calls() メソッドも使えます。 このメソッドは、call オブジェクトで作成した呼び出しのリストを受け取ります。 その一連の呼び出しが mock_calls に含まれていれば、アサートは成功します。

>>> m = MagicMock()
>>> m().foo().bar().baz()
<MagicMock name='mock().foo().bar().baz()' id='...'>
>>> m.one().two().three()
<MagicMock name='mock.one().two().three()' id='...'>
>>> calls = call.one().two().three().call_list()
>>> m.assert_has_calls(calls)

モックに対する呼び出しが m.one().two().three() という連鎖した呼び出しだけでなくても、アサートは成功します。

モックに複数回の呼び出しがあっても、その 一部 だけをアサートしたい場合があります。 順序も問わない場合は、assert_has_calls に any_order=True を渡せます。

>>> m = MagicMock()
>>> m(1), m.two(2, 3), m.seven(7), m.fifty('50')
(...)
>>> calls = [call.fifty('50'), call(1), call.seven(7)]
>>> m.assert_has_calls(calls, any_order=True)

より複雑な引数の照合

ANY と同じ基本的な考え方でマッチャーを実装し、モックに引数として渡されたオブジェクトに対して、より複雑なアサートを行えます。

オブジェクトの同一性に基づいて等価性を比較するオブジェクトが、モックに渡されることを期待しているとします (Python のユーザー定義クラスのデフォルトの比較方法です)。 assert_called_with() を使うには、まったく同じオブジェクトを渡す必要があります。 そのオブジェクトの一部の属性だけを調べたい場合は、それらの属性を検査するマッチャーを作成できます。

この例では、assert_called_with の「通常の」呼び出しでは不十分であることがわかります。

>>> class Foo:
...     def __init__(self, a, b):
...         self.a, self.b = a, b
...
>>> mock = Mock(return_value=None)
>>> mock(Foo(1, 2))
>>> mock.assert_called_with(Foo(1, 2))
Traceback (most recent call last):
    ...
AssertionError: expected call not found.
Expected: mock(<__main__.Foo object at 0x...>)
Actual: mock(<__main__.Foo object at 0x...>)

Foo クラスの比較関数は、例えば次のようになります。

>>> def compare(self, other):
...     if not type(self) == type(other):
...         return False
...     if self.a != other.a:
...         return False
...     if self.b != other.b:
...         return False
...     return True
...

このような比較関数を等価性の判定に使うマッチャーオブジェクトは、例えば次のようになります。

>>> class Matcher:
...     def __init__(self, compare, some_obj):
...         self.compare = compare
...         self.some_obj = some_obj
...     def __eq__(self, other):
...         return self.compare(self.some_obj, other)
...

全てをつなぎ合わせて:

>>> match_foo = Matcher(compare, Foo(1, 2))
>>> mock.assert_called_with(match_foo)

Matcher は、比較関数と、比較対象の Foo オブジェクトを渡してインスタンス化します。 assert_called_with の中で Matcher の等価性比較メソッドが呼ばれ、 モックに渡されたオブジェクトと、マッチャーの作成時に指定したオブジェクトを比較します。 一致すれば assert_called_with は成功し、一致しなければ AssertionError を送出します。

>>> match_wrong = Matcher(compare, Foo(3, 4))
>>> mock.assert_called_with(match_wrong)
Traceback (most recent call last):
    ...
AssertionError: Expected: ((<Matcher object at 0x...>,), {})
Called with: ((<Foo object at 0x...>,), {})

少し変更すれば、比較関数から直接 AssertionError を送出し、より役立つ失敗メッセージを示すこともできます。

Python のテストライブラリ PyHamcrest は、バージョン 1.5 以降、 等価性マッチャー (hamcrest.library.integration.match_equality) として同様の機能を提供しており、ここで役立つかもしれません。