---
# System prepended metadata

title: Mock
tags: [Python]

---

---
tags: Python
---
# Mock

## `MagicMock` vs `Mock`

`MagicMock` 實作 `Mock` 類別絕大多數的 magic methods，**如果沒有需要實作 magic methods 的話，官方推薦直接使用 `MagicMock`**。

> `MagicMock` is a subclass of `Mock` with default implementations of most of the magic methods. You can use `MagicMock` without having to configure the magic methods yourself.


## spec

預設的 Mock 存取任何屬性、呼叫任何方法都會回傳 Mock。

```python
m = Mock()
print(m.attribute)
# <Mock name='mock.attribute' id='4517941776'>

print(m.func())
# <Mock name='mock.func()' id='4461132944'>
```

但這樣的行為表現可能就顯得不真實。如果嚴謹一點，可以使用 spec 來做規範，避免 Mock 呼叫到不屬於他的函式。

```python
class Person:

    def __init__(self):
        self._name = 'Celine'

    @property
    def name(self):
        return self._name
```

```python
m = Mock(spec=Person())
print(m.name)
# <Mock name='mock.name' id='4519258384'>

print(m.attribute)
# AttributeError: Mock object has no attribute 'attribute'
```

除了給與 class 外，也可以透過 array 的方式定義。

```python
m = Mock(spec=['name'])
```

除了在 `Mock` 宣告外，也可以在 `patch` 使用，會與建立的 `Mock` 有相同的 spec。

```python
with patch('__main__.Person', spec=True) as m:
    m.name = 'Celine'
    person = Person()
    print(person.name)
    # Celine

    print(person.attribute)
    # AttributeError: Mock object has no attribute 'attribute'
```

## Return value of child mocks

方便一點可以透過 dictionary 的方式設定個別的回傳值。

```python
m = Mock(**{'method.return_value': 3, 'other.side_effect': KeyError})
m.method()
# 3

m.other()
# raise KeyError
```

## patch

`patch` 可使用 decorator 的方式，以下為了方便才使用 context manager。

### patch.dict

會以 update 的方式假造 dictionary，跳脫測試外時則恢復原狀。

```python
import os

print(os.environ)
# environ({'HOME': '/Users/celineyeh', 'LANG': 'en_US.UTF-8', ...})

with patch.dict('os.environ', {'HOME': '/NewHome'}) as m:
    print(os.environ)
    # environ({'HOME': '/NewHome', 'LANG': 'en_US.UTF-8', ...})
    
print(os.environ)
# environ({'HOME': '/Users/celineyeh', 'LANG': 'en_US.UTF-8', ...})
```

## wraps

在 mock 的同時，照樣去執行原先的 function。

```python
m = Mock(wraps=Person())
m.greet()
# Hi Celine

m.mock_calls
# [call.greet()]
```