C# 异步任务的单元测试<;行动结果>;
我对ASP.NET核心MVC非常陌生,我想知道是否有人能帮我解决我的问题 我在一个项目中工作,该项目将获得特定azure devops组织中的所有项目 这是我的控制器代码:C# 异步任务的单元测试<;行动结果>;,c#,unit-testing,asp.net-core-mvc,C#,Unit Testing,Asp.net Core Mvc,我对ASP.NET核心MVC非常陌生,我想知道是否有人能帮我解决我的问题 我在一个项目中工作,该项目将获得特定azure devops组织中的所有项目 这是我的控制器代码: public async Task<ActionResult> Organization(string selectedOrg, string oauth) { var client = new HttpClient(); IndexViewModel model = new IndexViewM
public async Task<ActionResult> Organization(string selectedOrg, string oauth)
{
var client = new HttpClient();
IndexViewModel model = new IndexViewModel();
model.Organizations = OrganizationData.Data;
if (selectedOrg == null)
{
selectedOrg = model.Organizations.FirstOrDefault().OrgName;
}
else
{
model.SelectedOrg = selectedOrg;
}
var token = _cache.Get<TokenModel>("Token" + HttpContext.Session.GetString("TokenGuid"));
oauth = token.AccessToken;
var url = "https://dev.azure.com/" + selectedOrg + "/_apis/projects?api-version=4.1";
try
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", oauth);
var response = await client.GetAsync(url);
var responseBody = response.Content.ReadAsStringAsync().Result;
model.Projects = JsonConvert.DeserializeObject<ProjectsModel>(responseBody);
client.Dispose();
return View("Index", model);
}
catch(Exception e)
{
client.Dispose();
return Json(Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(e.ToString()));
}
}
公共异步任务组织(字符串selectedOrg,字符串oauth)
{
var client=新的HttpClient();
IndexViewModel model=新的IndexViewModel();
model.Organizations=OrganizationData.Data;
if(selectedOrg==null)
{
选择org=model.Organizations.FirstOrDefault().OrgName;
}
其他的
{
model.SelectedOrg=SelectedOrg;
}
var token=_cache.Get(“token”+HttpContext.Session.GetString(“TokenGuid”);
oauth=token.AccessToken;
变量url=”https://dev.azure.com/“+selectedOrg+”/_api/projects?api版本=4.1”;
尝试
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization=新的AuthenticationHeaderValue(“承载者”,oauth);
var response=wait client.GetAsync(url);
var responseBody=response.Content.ReadAsStringAsync().Result;
model.Projects=JsonConvert.DeserializeObject(responseBody);
client.Dispose();
返回视图(“索引”,模型);
}
捕获(例外e)
{
client.Dispose();
返回Json(Newtonsoft.Json.JsonConvert.DeserializeObject(e.ToString());
}
}
有谁能帮我用这个做单元测试吗?还是我必须重构这个 对于许多依赖项,您有方法 为什么方法的签名传递一个从未使用过的
oauth
值
首先,在控制器内通过http调用任何外部依赖项都是不可取的。这整件事应该被抽象成它自己的要求。因为它看起来是在获取数据,所以实际上应该是在您的数据层。用单独的项目覆盖整个n层方法很可能超出范围,因此我认为,让我们只覆盖单元测试的最小值
首先,您需要抽象您的HttpClient。如果单元测试方法在其自身之外进行任何调用(大部分情况下),您就无法真正执行单元测试方法,因为这不是单元测试,而是集成测试
// I don't have a full grasp of your complete eco-system so based on the
// minimal information provided, this would at least get you close
public interface IAzureAPI
{
public Task<string> GetOrgAsync(string org, string oauth);
}
public class AzureAPI : IDisposable
{
public async Task<string> GetOrgAsync(string org, string oauth)
{
// use *using* not try/catch/finally/dispose
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", oauth);
var url = "https://dev.azure.com/" + org+ "/_apis/projects?api-version=4.1";
var response = await client.GetAsync(url);
// never use `.Result` unless you absolutely know what you are doing
// always using async/wait if possible
var result = await response.Content.ReadAsStringAsync();
return result;
}
}
}
现在谈谈最难的部分:
public async Task<ActionResult> Organization(string selectedOrg, string oauth)
{
IndexViewModel model = new IndexViewModel();
// I have no idea where model came from so
// this appears to block "unit-testing"
// strange that you don't validate `selectedOrg`, you just use it
model.Organizations = OrganizationData.Data;
if (selectedOrg == null)
{
selectedOrg = model.Organizations.FirstOrDefault().OrgName;
}
else
{
model.SelectedOrg = selectedOrg;
}
// no idea where `_cache` came from so
// also appears to block "unit-testing"
// As does `HttpContext` because you aren't using the
// Interface
var token = _cache.Get<TokenModel>("Token" + HttpContext.Session.GetString("TokenGuid"));
oauth = token.AccessToken;
try
{
var orgInfo = await _azureAPI.GetOrgAsync(selectedOrg, oauth);
model.Projects = JsonConvert.DeserializeObject<ProjectsModel>(orgInfo);
// return a view here???
return View("Index", model);
}
catch(Exception e)
{
// return JSON here instead????
return Json(Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(e.ToString()));
}
}
公共异步任务组织(字符串selectedOrg,字符串oauth)
{
IndexViewModel model=新的IndexViewModel();
//我不知道模特是从哪里来的
//这似乎阻止了“单元测试”
//奇怪的是,你没有验证'selectedOrg',你只是使用它
model.Organizations=OrganizationData.Data;
if(selectedOrg==null)
{
选择org=model.Organizations.FirstOrDefault().OrgName;
}
其他的
{
model.SelectedOrg=SelectedOrg;
}
//不知道`_cache`是从哪里来的
//也似乎阻止了“单元测试”
//“HttpContext”也是如此,因为您没有使用
//接口
var token=_cache.Get(“token”+HttpContext.Session.GetString(“TokenGuid”);
oauth=token.AccessToken;
尝试
{
var orgInfo=await _azureAPI.getorgancy(selectedOrg,oauth);
model.Projects=JsonConvert.DeserializeObject(orgInfo);
//在这里返回视图???
返回视图(“索引”,模型);
}
捕获(例外e)
{
//在这里返回JSON吗????
返回Json(Newtonsoft.Json.JsonConvert.DeserializeObject(e.ToString());
}
}
这是一个一般性的开始,但是有太多的未知数和太多的依赖项,无法真正编写一个真正的单元测试。下面是一个基于您提供的信息的快速结构和半测试
public MyControllerTests
{
// for 100% Cover Coverage you'd need all of these
public async Task Organization_OrgAsString_ReturnsView
{
//...
}
public async Task Organization_OrgAsNull_ReturnsView
{
// Arrange
var azureAPI = Substitute.For<IAzureAPI>();
azureAPI.GetOrgAsync(null, null)
.Returns("somestring");
var controller = new MyController(azureAPI);
// Act
var result = await controller.Organization(null, null);
// Assert
Assert.That(result....);
}
public async Task Organization_WithException_ReturnsJson
{
//...
}
}
公共MyControllerTests
{
//对于100%的保险范围,您需要所有这些
公共异步任务组织\u OrgaString\u ReturnsView
{
//...
}
公共异步任务组织\u OrgAsNull\u ReturnsView
{
//安排
var azureAPI=替换为();
azureAPI.GetOrgAsync(null,null)
.Returns(“somestring”);
var控制器=新的MyController(azureAPI);
//表演
var result=await controller.Organization(null,null);
//断言
断言(结果…);
}
公共异步任务组织\u带异常\u返回
{
//...
}
}
但是,该代码与实现问题紧密耦合,不允许单独进行单元测试。理想情况下,您希望能够模拟/存根所有依赖项,并将它们注入测试主题。感谢您对这一点的评论。是否可以仅针对json结果进行单元测试?您需要提取http调用,以便伪造json响应。我还意识到您将阻塞.result
与异步等待混合在一起,这可能会导致死锁。这很难回答,因为有许多问题需要考虑。首先,更改response.Content.ReadAsStringAsync().Result
towait response.Content.ReadAsStringAsync()
解决@nkosi提到的.Result
问题。然后使用您最喜欢的搜索引擎了解为什么创建这样的HttpClient
s是个坏主意,以及您可能希望如何使用依赖项注入。最后,通过抽象,@nkosi可能意味着创建另一个类来处理对Azure端点的请求,并将其注入控制器中。使用Nsubstitute,异步方法不需要显式返回Task
:azureAPI.GetOrgAsync(null,null)。返回(“somestring”)代码>
public MyControllerTests
{
// for 100% Cover Coverage you'd need all of these
public async Task Organization_OrgAsString_ReturnsView
{
//...
}
public async Task Organization_OrgAsNull_ReturnsView
{
// Arrange
var azureAPI = Substitute.For<IAzureAPI>();
azureAPI.GetOrgAsync(null, null)
.Returns("somestring");
var controller = new MyController(azureAPI);
// Act
var result = await controller.Organization(null, null);
// Assert
Assert.That(result....);
}
public async Task Organization_WithException_ReturnsJson
{
//...
}
}