あいつの日誌β

働きながら旅しています。

Failed py.test using httpretty contain redis client

Failed py.test using httpretty contain redis client

Story

I am faced with a problem that test codes failed. for example external web api should return mock data when we testing.

たとえば課金API のレスポンスなどテスト時においては HTTP Request に関しては Mock したい時ってありますよね。 そこで httpretty を使っていたら Redis を使ったテストで遭遇した問題を共有します。

What HTTPretty is

https://hub.points.com/h/i/5197678-mock-your-requests-with-httpretty

Problem & Solution

this code have problem.

import redis
import httpretty

r = redis.Redis()
httpretty.enable()
print r.ping()

I soleved like this.

import redis
import httpretty

r = redis.Redis()
print r.ping()
httpretty.enable()
print r.ping()

What's happen ?

redis.client.Redis use connection if some connection has been alived.

redis.client.Redis は connection_pool にすでに connection があったらそれを再利用します

# redis-py/redis/client.py
    def execute_command(self, *args, **options):
        "Execute a command and return a parsed response"
        pool = self.connection_pool
        command_name = args[0]
        connection = pool.get_connection(command_name, **options)

for example:

import redis

r = redis.Redis()
print r.connection_pool._available_connections  # []
print r.ping()                                  # True
print r.connection_pool._available_connections  # [<redis.connection.Connection object at 0x210ac10>]
print r.ping()                                  # True

These connections created function from socket module. And HTTPretty replace that function to monkey patching.

そしてその connection を作る時に soket モジュールを使って connection を作っています。 HTTPretty は httpretty.enable() をした時に soket モジュールに monkey patching しています

# httpretty/core.py

    @classmethod
    def enable(cls):
        cls._is_enabled = True 
        socket.socket = fakesock.socket
        socket._socketobject = fakesock.socket
        socket.SocketType = fakesock.socket

        socket.create_connection = create_fake_connection
        socket.gethostname = fake_gethostname
        socket.gethostbyname = fake_gethostbyname
        socket.getaddrinfo = fake_getaddrinfo

I added comment to earlier examle codes.

先ほどのコードでは以下の現象が起きています

import redis
import httpretty

r = redis.Redis()
httpretty.enable()  # repleaced create_socket function to mokey pattching
print r.ping()      # created monkey patched socket and it doesn't work
import redis
import httpretty

r = redis.Redis()
print r.ping()      # created connection by correct function
httpretty.enable()  # repleaced create_socket function to mokey pattching
print r.ping()      # use socket in r.connection_pool._available_connections 

I avoid this probrem like this. preparing connection when httpretty.is_enable == False. In this case redis client use available connection Even if httpretty.is_enable == True as long as connection is alive.

というわけでソケットを作るタイミングを意識することによってこの問題を回避しました。 テストケースで私がやりたいことはこれで達成できました。めでたし。