はじめに
pytestで単体テストを書いているときに以下のようなエラーに遭遇したことはないでしょうか。
ScopeMismatch: You tried to access the 'function' scoped fixture 'monkeypatch' with a 'module' scoped request object, involved factories
この記事では、このエラーについて解説し解決策を示します。
エラーの解説
エラーメッセージに書いてある通り、functionスコープのfixtureであるmonkeypatchをより広いmoduleスコープで使おうとしたことによって、スコープがあっていないよ!と言われています。
pytestではfixtureの中で別のfixtureを入力として受け取り、使用することができます。この時気をつけなくてはいけないのは、fixtureの「スコープ」という概念です。スコープには以下の4つがあります1。
- 
function: テストケース毎に実行される(デフォルト) - 
class: テストケースをまとめたクラス毎に実行される - 
module: モジュール毎に実行される - 
session: すべてのテストケースで一回だけ実行される 
もちろん、下に行くほどスコープが広いです。
**狭いスコープのfixtureをより広いスコープで使うことはできません。**つまり、functionスコープのfixtureはclass以上のスコープを持つfixtureで使うことはできません。
私は、monkeypatchを広いスコープで使おうとして、最初に示したエラーにたまに遭遇してしまいます。以下では、そういうシーンでの解決策を示していきます。
解決策の前に
まず、monkeypatchを広いスコープで使う必要が本当にあるかを考えます。ただ、一回実行すればいいからという理由だけでclassスコープやmoduleスコープにしていないでしょうか。monkeypatchの「メソッドや属性を差し替える」という機能は意図しない箇所へ悪影響を及ぼす可能性があるので、できるだけその範囲(スコープ)を狭くする方が安全です。よって、毎回実行する必要がなくても、基本的にはmonkeypatchはテストケース毎に実行するようにするべきです。
とはいえ、実行時間などの問題からそうもいかずmonkeypatchを広いスコープで使わざるを得ない場合もあります。
解決策
以下のようなsessionスコープ版のmonkeypatchを自作します。最も広いsessionスコープで作っておけば任意のスコープで使用することができます。
import pytest
from _pytest.monkeypatch import MonkeyPatch
@pytest.fixture(scope="session")
def monkeypatch_session():
    mpatch = MonkeyPatch()
    yield mpatch
    mpatch.undo()
ソースコードを読むとわかりますが、monkeypatch_sessionはmonkeypatchと全く同じ処理をしているので、monkeypatchでできることはすべてできます。あとは、monkeypatchの代わりにmonkeypatch_sessionを使うだけです。
# これはScopeMismatchになる
@pytest.fixture(scope="module")
def module_fixture(monkeypatch):
    monkeypatch.setattr(...)
    ...
# monkeypatchをそのままmonkeypatch_sessionに置き換える
@pytest.fixture(scope="module")
def module_fixture(monkeypatch_session):
    monkeypatch_session.setattr(...)
    ...
一応、MonkeyPatchをそのまま使うこともできます。
@pytest.fixture(scope="module")
def module_fixture():
    with MonkeyPatch.context() as mp:
        mp.setattr(...)
        ...
        yield
注意点
- 上にも書いたように、広いスコープで
monkeypatchを使うのは安全ではない - 
monkeypatch_sessionで使用しているMonkeyPatchはユーザが使うことを意図していない(_pytest内にある)ものなので、予告なく変更される可能性がある 
以上の注意点を認識して、用法用量を守って正しくお使いください。
- 
実は
moduleより広く、sessionより狭いpackageというスコープもあります(pytestドキュメント) ↩