ExpressionTreesがWebApiをテストする方法

ApiControllersはすべての人に適していますが、WSDLを作成するわけではなく、プロキシを取得および取得することはできません。 はい、ApiControllersは単体テストでかなりよくテストされています。 しかし、ユニットにはトランスポートレベルのエラーがないため、一般的に、エンドツーエンドのスクリプトをいくつか使用しないと不便です。 もちろん、我慢してHttpClientを取得し、次のように記述できます。



HttpClient client = new HttpClient(); client.BaseAddress = new Uri("http://localhost:56851/"); // Add an Accept header for JSON format. client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); HttpResponseMessage response = client.GetAsync("api/User").Result; if (response.IsSuccessStatusCode) { var users = response.Content.ReadAsAsync<IEnumerable<Users>>().Result; usergrid.ItemsSource = users; } else { MessageBox.Show("Error Code" + response.StatusCode + " : Message - " + response.ReasonPhrase); }
      
      





しかし、コントローラーの説明に入り、タイプを確認するために毎回どのように退屈になりますか、要するに、私はこれが好きです:

 var resp = GetResponse<SomeController>(c => gc.SomeAction(new Dto(){val = "123"}));
      
      





判明したように、これはいくつかのストリートマジックを表現ツリーに適用することで実現できます



API情報を取得する

まず、どのAPIが存在するかを知る必要があります。そのために、ルートをマッピングします

 [SetUp] public void SetUp() { _cfg = new HttpConfiguration(); _cfg.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); }
      
      





リモートメソッド呼び出し

ApiDescriptionsは、コントローラーを探す場所を知っており、親切にメタ情報を提供します。 WebApiには、1つのメソッドを呼び出すための多くのオプションがあります。1つのAPIメソッドに2つのHttpメソッドを使用することはないため、この場合は気にしません。 明確な良心をもって、最初の適切な方法を採用します

 protected HttpResponseMessage GetResponse<T>(Expression<Action<T>> expression) where T : ApiController { var baseAddress = System.Configuration.ConfigurationManager.AppSettings["BaseAddress"]; var convert = (MethodCallExpression)expression.Body; var name = convert.Method.Name; var pars = convert.Method.GetParameters().ToArray(); var desc = _cfg.Services.GetApiExplorer().ApiDescriptions.First( d => d.ActionDescriptor.ControllerDescriptor.ControllerType == typeof(T) && d.ActionDescriptor.ActionName == name); //...
      
      







仮定番号2。 JSON以外、私は何にも興味がありません。 パラメータ内のgetメソッドおよびpost-withプリミティブの場合、paramName = {paramName}という形式のオカレンスをparamName =渡したExpressionの値に置き換えます。

 using (var client = new HttpClient { BaseAddress = new Uri(baseAddress) }) { client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); var relPath = desc.RelativePath; var index = 0; if (relPath.Contains("?")) { foreach (var p in pars) { relPath = relPath.Replace( string.Format("{{{0}}}", pars.Name), InvokeExpression(convert.Arguments[index++], p.ParameterType).Return(o => o.ToString(), string.Empty)); } }
      
      





InvokeExpression

Expressionの値を取得する最も簡単な方法は、それをラムダにコンパイルすることです。 率直に言って、コンパイル時の戻り値の型はコントローラーの型からわかります。 ただし、この場合は、voidを返すメソッドに対して別のケースを作成する必要があります。 この場合、Func <T、TResult>の代わりにActionを使用する必要があります。 そのようなコードは理解するのがすでにかなり複雑です。 私はパフォーマンスを追求しません。ネットワークコストは、コンパイル時に節約されるナノ秒をすべて消費します。

 private static object InvokeExpression(Expression e, Type returnType) { return Expression.Lambda( typeof (Func<>).MakeGenericType(returnType), e).Compile().DynamicInvoke(); }
      
      





結果を取得する

一番簡単なことは、結果を取得してメソッドを呼び出すことです。 Postメソッドでは、常に親ラッパーオブジェクトがあると想定しています。 結果を返すか、エラーが発生します。

 var uri = new Uri(new Uri(baseAddress), relPath); var resp = desc.HttpMethod.Method == HttpMethod.Post.ToString() ? client.PostAsJsonAsync(uri.ToString(), InvokeExpression(convert.Arguments.Single(), desc.ParameterDescriptions.Single().ParameterDescriptor.ParameterType)).Result : client.GetAsync(uri).Result; if (resp.StatusCode == HttpStatusCode.InternalServerError) { using (var sr = new StreamReader(resp.Content.ReadAsStreamAsync().Result)) { throw new InvalidOperationException(sr.ReadToEnd()); } } return resp;
      
      





最後に

結果はこのメソッドです

 protected HttpResponseMessage GetResponse<T>(Expression<Action<T>> expression) where T : ApiController { var baseAddress = System.Configuration.ConfigurationManager.AppSettings["BaseAddress"]; var convert = (MethodCallExpression)expression.Body; var name = convert.Method.Name; var pars = convert.Method.GetParameters().ToArray(); var desc = _cfg.Services.GetApiExplorer().ApiDescriptions.First( d => d.ActionDescriptor.ControllerDescriptor.ControllerType == typeof(T) && d.ActionDescriptor.ActionName == name); using (var client = new HttpClient { BaseAddress = new Uri(baseAddress) }) { client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); var relPath = desc.RelativePath; var index = 0; if (relPath.Contains("?")) foreach (var p in pars) { relPath = relPath.Replace( string.Format("{{{0}}}", p.Name), InvokeExpression(convert.Arguments[index++], p.ParameterType).Return(o => o.ToString(), string.Empty)); } var uri = new Uri(new Uri(baseAddress), relPath); var resp = desc.HttpMethod.Method == HttpMethod.Post.ToString() ? client.PostAsJsonAsync(uri.ToString(), InvokeExpression(convert.Arguments.Single(), desc.ParameterDescriptions.Single().ParameterDescriptor.ParameterType)).Result : client.GetAsync(uri).Result; if (resp.StatusCode == HttpStatusCode.InternalServerError) { using (var sr = new StreamReader(resp.Content.ReadAsStreamAsync().Result)) { throw new InvalidOperationException(sr.ReadToEnd()); } } return resp; } }
      
      





このコードの多くの部分は完璧ではありませんが、目標を達成します。今ではこのようなテストを書くことができます。

 [Test] public void UserController_TokenValid_WrongTokenReturnFalse() { var resp = GetResponse<UserController>(gc => gc.TokenValid("123")); Assert.AreEqual(false, resp.Content.ReadAsAsync<bool>().Result); }
      
      





または、より複雑な例:

 var obj = new RoundResultDtoIn() { LevelId = 3, RoomName = "123", RoundTime = 50, StartDateTime = DateTime.Now }; GetResponse<GameController>(gc => gc.SaveResults(obj));
      
      






All Articles