httptestでmock server的なものを作る方法のメモ
以下の3つが欲しい
- get
- post (form)
- post (json)
package m import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "net/url" "testing" "io/ioutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGet(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Exactly(t, "/foo", r.URL.Path) assert.Exactly(t, "1", r.URL.Query().Get("value")) })) defer ts.Close() _, err := http.Get(ts.URL + "/foo?value=1") require.NoError(t, err) } func TestPost(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Exactly(t, "/foo", r.URL.Path) r.ParseForm() assert.Exactly(t, "1", r.Form.Get("value")) })) defer ts.Close() values := url.Values{} values.Add("value", "1") _, err := http.PostForm(ts.URL+"/foo", values) require.NoError(t, err) } func TestPostJSON(t *testing.T) { type data struct { Name string `json:"name"` Age int `json:"int"` } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Exactly(t, "/foo", r.URL.Path) var val data parseJSONRequest(r, func(b []byte) error { return json.Unmarshal(b, &val) }) assert.Exactly(t, "foo", val.Name) assert.Exactly(t, 20, val.Age) })) defer ts.Close() dataset := data{ Name: "foo", Age: 20, } b, err := json.Marshal(dataset) require.NoError(t, err) req, err := http.NewRequest("POST", ts.URL+"/foo", bytes.NewBuffer(b)) req.Header.Set("Content-Type", "application/json") _, err = (&http.Client{}).Do(req) require.NoError(t, err) } func parseJSONRequest(r *http.Request, parse func(body []byte) error) error { if r.Body == nil { return errors.New("missing form body") } ct := r.Header.Get("Content-Type") if ct != "application/json" { return errors.Errorf("invalid content type: %v", ct) } b, err := ioutil.ReadAll(r.Body) if err != nil { return err } return parse(b) }
magicalimportというライブラリを作ってました
magicalimportというライブラリを作ってました。
はじめに
これは何をするライブラリかというと物理的なファイル名を指定して、指定したファイルをpython moduleとしてimportするためのライブラリです。
用途
例えばconfigファイルの読み込みに便利かもしれません。
使い方
例えば、以下のようなファイル構造の時に、以下のようなfoo.pyがあった時に。
. ├── a │ └── b │ └── c │ └── foo.py └── main.py
a/b/c/foo.py
name = "foo" _age = "*secret*"
main.pyでは以下の様なコードでfoo.pyを読み込むことができます。
from magicalimport import import_from_physical_path foo = import_from_physical_path("./a/b/c/foo.py")
ちなみに、importするmodule名を指定する事もできて、 as_
オプションを付けます。sys.modulesに登録されるのでその後は普通にimportできます。
from magicalimport import import_from_physical_path foo = import_from_physical_path("./a/b/c/foo.py", as_="foo2") import foo2 # fooとfoo2は同じもの
注意点
moduleの階層構造に関係なくimportしているところがあるので読み込んだ先のファイルでrelative importなどは上手くいかないです。
例えば、以下のような設定ファイルの構造でlocal.py,test.pyがbase.pyの設定を共有したいときなどに。
config ├── base.py ├── test.py └── local.py
普通にrelative importしたくなりますがこれは動きません。
from .base import *
以下の様に書く必要があります。star importする場合にはexpose_all_membersを使うと便利です。
# 以下はだいたい `from .base import *` と同じ import magicalimport import os.path here = os.path.dirname(os.path.abspath(__file__)) base = magicalimport.import_from_physical_path(os.path.join(here, "./base.py"), as_="base") magicalimport.expose_all_members(base)
moduleの"_"で始まるものはimportされないですが。
追記
python2もサポートしました
追記
here オプションをサポートしました。以下の様に書ける様になりました。
import magicalimport base = magicalimport.import_from_physical_path("base.py", as_="base", here=__file__) magicalimport.expose_all_members(base)