Mock requests แบบสวยๆ ด้วย responses

Yothin Muangsommuk
2 min readJul 24, 2018

--

สิ่งหนึ่งที่เราเจอไม่ว่าจะช้าหรือเร็วคือ เมื่อเขียน Python ไปซักพักเราต้องยุ่งกับการยิง HTTP request ไปหา service ข้างนอก ซึ่ง Library ที่นิยมที่สุดในการทำสิ่งนี้ใน Python ก็คงหนีไม่พ้น requests นะครับ

สมมติเรามีฟังก์ชั่น process_something ที่ทำหน้าที่ยิง requests ไปหา service ข้างนอกแล้วคืนของออกมาเป็น dictionary หน้าตาประมาณนี้

process_something.py

ถ้าเขียนแบบสมัยก่อน เราก็จะต้อง patch ตัว requests.get แล้วสร้าง Mock() object มารับ return_value หน้าตาประมาณนี้

ซึ่งพอทำแบบนี้บ่อยๆ เราก็เริ่มเห็น Pattern ซ้ำๆ ครับว่าต้อง Mock status_code นะ json() call นะหรือแม้กระทั่ง content type กับ headers ในบางเคส

วิวัฒนาการถัดมาของการ mock requests คือ พอเราเห็น pattern ซ้ำๆ เราก็จับมันมาทำ utils function ครับ ซึ่งหน้าตามันก็จะออกมาประมาณนี้

แต่วิธีนี้ก็ยังต้องใช้ร่วมกับ assert_called_once_with เพื่อที่จะรับประกันว่าการที่เรา mock เนี่ยมันถูกใช้งานทุกครั้ง ไม่ใช่ mock free

Responses

ผมไปเจอ Package นี้ตอนกด Discovery ใน GitHub แล้วคน Star เยอะมาก (ณ เวลาเขียนก็ 1955 Stars) แล้วคนสร้างก็ไม่ใช่ใครที่ไหนไกล เป็น Sentry ซึ่งเป็น service ที่เราใช้ในการ catch exception เรานั่นเอง (สนใจอ่าน blog ว่าเราใช้ Sentry ยังไงบ้างได้ที่นี่ครับ) วิธีลงก็ง่ายมากครับเหมือน library Python ทั่วไปคือ

$ pip install responses

หลังจากเราลง Package นี้ไปแล้วเราก็สามารถใช้มันในการ Mock response object ของ HTTP request ที่เราเรียกได้แล้วครับ หน้าตาก็จะประมาณนี้

test_process_something.py

จะเห็นว่าหน้าตา signature ของ responses.add ค่อนข้างจะใกล้เคียงกับ ที่เราเคย mock ก่อนหน้านี้เลยคือรับ HTTP METHOD, URL, JSON return และ status code ซึ่งตัว responses เองเนี่ยสามารถใช้ได้ทั้งแบบ decorator แบบในภาพและแบบ context manager ผ่าน responses.RequestsMock() ก็ได้ครับ

ความเจ๋งอีกอย่างนึงที่ responses ทำให้เราก็คือ เราไม่ต้องมานั่งเช็คว่า assert_called_once_with ตัว mock request เราแล้วครับ เพราะว่าตัว Library เองจะจัดการให้เลยผ่าน flag ที่ชื่อว่า assert_all_requests_are_fired ซึ่งค่า default ของมันจะเป็น True อยู่แล้ว แต่เราจะใช้ความสามารถนี้ได้ต้องใช้ responses.RequestsMock() หรือใช้ผ่าน context manager นะครับ

นอกจากจะ assert request ให้เราแล้ว อีกความสามารถนึงที่เจ๋งมากคือ เราสามารถเพิ่ม callback function ให้มัน return response แบบ dynamic ได้ครับ หน้าตาก็ประมาณนี้ (ตัวอย่างผมเอามาจาก README.md ของ Library นะครับ)

ยังมีความสามารถอื่นๆ อีกนะครับที่ผมยังไม่ได้พูดถึง แต่ที่พูดถึงมานี่คือ Use Case ที่ทีมเราเอามาใช้แล้ว มันสะดวกและสวยมาก ถ้าอยากศึกษาเพิ่มเติมตามไปดูได้ที่ GitHub repo ของ Responses เลยครับ

--

--

Yothin Muangsommuk

Pythonista @ProntoTools ♥ Python, Django, Vim and Star Trek 🖖