【问题标题】:Faking GetAsync call from test伪造来自测试的 GetAsync 调用
【发布时间】:2019-10-10 07:26:03
【问题描述】:
public Fixture()
{
      _server = new TestServer(new WebHostBuilder()    
         .UseStartup<Startup>()
           .ConfigureServices(services =>
           {
              services.AddScoped<ICarService, CarService>();
           }));

      Client = _server.CreateClient();
}

从测试中我使用这个HttpClient 来测试我的API。

using (var response = await _client.GetAsync($"/api/car/{id}"))
{
    //...         
}

问题是我想在CarService 类中伪造GetAsync(int id) 方法的结果。

所以我尝试了

var myCarObject = ... omitted for clarity
var myCarMockService = new Mock<ICarService>();
myCarMockService.Setup(x => x.GetAsync(It.IsAny<int>())).Returns(Task.FromResult(myCarObject));

我不知道这是正确的方法,但如果是我该如何注入它 进入Fixture 类,以便CarService 可以使用它。

public class CarService: ICarService {
    private readonly CarDbContext _carDbContext;

    public CarService(CarDbContext carDbContext)
    {
        _carDbContext = carDbContext;
    }

    public async Task<Car> GetAsync(int id)
    {
        return await _carDbContext.Cars.FindAsync(id);
    }
}

更新:

private readonly ICarService _carService;

public CarController(ICarService carService)
{
    _carService = carService;
}

public async Task<IActionResult> Get([FromRoute] int id)
{
    var car = await _carService.GetAsync(id);
}

更新 2:

public class Startup
{
   public void ConfigureServices(IServiceCollection services)
   {
       services.AddDbContext<CarDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("Db")); });
       services.AddTransient<ICarService, CarService>();
   }
}

public class CarService : ICarService
{
    private readonly CarDbContext _carDbContext;

    public ContactService(CarDbContext carDbContext)
    {
       _carDbContext= carDbContext;
    }
    public async Task<Owner> GetAsync(int ownerId)
    {
       var owner = await _carDbContext.Owners.FindAsync(ownerId);
       return owner.Car;            
    }
}

更新 3:

private readonly TestServer _server;
public Fixture()
{
    var dbContextOptions = new DbContextOptionsBuilder<CarDbContext>()
                    .UseInMemoryDatabase(Guid.NewGuid().ToString())
                    .Options;

    var mockContext = new Mock<CarDbContext>(dbContextOptions);
    var mockOwnerSet = new Mock<DbSet<Owner>>();
    var mockCarSet = new Mock<DbSet<Car>>();
    mockContext.Setup(m => m.Owners).Returns(mockOwnerSet.Object);
    mockContext.Setup(m => m.Cars).Returns(mockCarSet.Object);          

    var carService = new CarService(mockContext.Object);

    _server = new TestServer(new WebHostBuilder()
        .ConfigureAppConfiguration((context, conf) =>
        {
            conf.AddJsonFile(@Directory.GetCurrentDirectory() + "../appsettings.json");
        }).UseStartup<Startup>()
            .ConfigureServices(services =>
            {
                services.AddDbContext<CarDbContext>(options => options.UseInMemoryDatabase("Test"));
                services.AddScoped<ICarService>(_ => carService);
            })
        );

    Client = _server.CreateClient();    
}   

【问题讨论】:

  • 有点不清楚你在问什么。你能分享一下MyService 的实现吗?
  • 你可以模拟 HTTPClient
  • @Johnny,我用服务实现更新了 q
  • 如果您使用 HttpClient 调用 API,这意味着您正在访问实际服务。如果您不打算使用实际服务,那么您应该只为CarService 编写单元测试。
  • @user1672994 我确实有意访问 CarService 我只是想从它的 GetAsync 方法中伪造结果。

标签: c# asp.net-core .net-core integration-testing


【解决方案1】:

配置测试服务器以使用模拟服务

public Fixture() {

    Car myCarObject = //... omitted for brevity
    var myCarMockService = new Mock<ICarService>();
    myCarMockService
        .Setup(x => x.GetAsync(It.IsAny<int>()))
        .ReturnsAsync(myCarObject);

    _server = new TestServer(new WebHostBuilder()
        .UseStartup<Startup>()
        .ConfigureTestServices(services => {
            var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(ICarService));
            if (serviceDescriptor != null) services.Remove(serviceDescriptor);
            services.AddTransient<ICarService>(_ => myCarMockService.Object); // <-- NOTE
        })
    );

    Client = _server.CreateClient();
}

这样,当调用被模拟的服务将按预期注入。

【讨论】:

  • 感谢您的努力,我确信这会起作用,但我仍然从 return await _carDbContext.Cars.FindAsync(id); 得到 null;
  • 否 这个例子替换了整个汽车服务。它应该调用您的实际实现类。我假设 ICarservice 被注入到控制器中?控制器应该使用模拟的 ICarService
  • 没错,我的服务正在使用 DI,但我仍然从 _carDbContext.Cars.FindAsync(id) 获得空值。看起来我也需要模拟 dbContext 吗? (如果是这样,我完全糊涂了:)
  • @user1765862 检查更新的答案。注意ConfigureTestServices
猜你喜欢
  • 2016-09-08
  • 1970-01-01
  • 1970-01-01
  • 2019-03-07
  • 1970-01-01
  • 1970-01-01
  • 2012-01-17
  • 2019-06-26
  • 2017-06-07
相关资源
最近更新 更多