pytest-mockでULIDをモックする方法

# 概要
ユニットテストを書いてる中で、メソッド内で生成されるulidの値を固定したくなったのでその方法について調べました。

```myapp/mock_sample.py
import ulid


class MockSampleClass:
    """クラス"""

    def create_ulid():
        """ULIDを生成します。"""

        return ulid.new().str    # ←これをモック化したい

```

# 最終的なコード

```success.py
from myapp.mock_sample import MockSampleClass


def test_mock_sample_create_ulid(mocker):
    """MockSampleClass.create_ulid()のテスト"""
    excepted = "01JJ5XBKCPV0PJN1CD3C2WHPXR"
    
    m = mocker.patch("ulid.new")
    type(m).str = excepted
    m.return_value = m

    result = MockSampleClass.create_ulid()
    assert result == excepted
```


# 調査過程
## 失敗1

まずは普通のモックのように書いてみます。

```failed1.py
from myapp.mock_sample import MockSampleClass


def test_mock_sample_create_ulid(mocker):
    """MockSampleClass.create_ulid()のテスト"""
    excepted = "01JJ5XBKCPV0PJN1CD3C2WHPXR"

    mocker.patch("ulid.new", return_value=excepted)

    result = MockSampleClass.create_ulid()
    assert result == excepted
```

```
FAILED tests/myapp/test_mock_sample.py::test_mock_sample_create_ulid - AttributeError: 'str' object has no attribute 'str'
```

strメソッドがなくてエラーとなりました。

## 失敗2

patchの引数を変えてみます。

```failed2.py

def test_mock_sample_create_ulid(mocker):
    """MockSampleClass.create_ulid()のテスト"""
    excepted = "01JJ5XBKCPV0PJN1CD3C2WHPXR"

    mocker.patch("ulid.new.str", return_value=excepted)

    result = MockSampleClass.create_ulid()
    assert result == excepted
```

```
FAILED tests/myapp/test_mock_sample.py::test_mock_sample_create_ulid - AttributeError: <bound method Api.new of <ulid.api.api.Api object at 0xffff7d149f70>> does not have the attribute 'str'
```

こちらもstrメソッドがなくてエラーとなりました。
エラー内容的に失敗1の方がまだ見込みがありそう。

## 失敗3

strのモック方法を検索して以下のサイトを見つけました。
こちらのサイトを参考に__str__のモックを見よう見まねで作ってみます。
※使い方がそもそもおかしい可能性があります。

https://zenn.dev/nyanchu/articles/pytest_mock_8da0886bbb9087#tl%3Bdr

patch()はデフォルトでMagicMockを作るらしいので、patchをインポートして使ってみます。

https://docs.python.org/ja/3.13/library/unittest.mock.html#unittest.mock.patch


```failed3.py
from unittest.mock import patch

from myapp.mock_sample import MockSampleClass


def test_mock_sample_create_ulid():
    """MockSampleClass.create_ulid()のテスト"""
    excepted = "01JJ5XBKCPV0PJN1CD3C2WHPXR"

    patcher = patch("ulid.new")
    mock = patcher.start()
    mock.__str__.return_value = excepted

    result = MockSampleClass.create_ulid()
    assert result == excepted
```

```
FAILED tests/myapp/test_mock_sample.py::test_mock_sample_create_ulid - AssertionError: assert <MagicMock name='new().str' id='281472642827072'> == '01JJ5XBKCPV0PJN1CD3C2WHPXR'
```

MagicMockのオブジェクトが返ってきていそうです。
感覚的にはもう一息です。

## 成功

前項のあと、かなり行ったり来たりしながら試行錯誤をしていたのですが、ふと__str__をモックする方法を探すのではなく、ULIDをモックする方法を調べたほうが早いのでは？と思い、検索すると以下の記事が引っかかりました。

https://qiita.com/poti_rev/items/372a551a2015d631947e

これだーーー！！と思い、参考にさせていただいて無事解決しました。

```success.py
from myapp.mock_sample import MockSampleClass


def test_mock_sample_create_ulid(mocker):
    """MockSampleClass.create_ulid()のテスト"""
    excepted = "01JJ5XBKCPV0PJN1CD3C2WHPXR"
    
    m = mocker.patch("ulid.new")
    type(m).str = excepted
    m.return_value = m

    result = MockSampleClass.create_ulid()
    assert result == excepted
```

# 所感
モックは奥が深いですね。

# 参考サイト

- https://zenn.dev/nyanchu/articles/pytest_mock_8da0886bbb9087#tl%3Bdr
- https://docs.python.org/ja/3.13/library/unittest.mock.html#unittest.mock.patch
- https://qiita.com/poti_rev/items/372a551a2015d631947e