xUnit的IClassFixture在每个测试用例中重新初始化。

huangapple go评论138阅读模式
英文:

xUnit IClassFixture reinitialized for every testcase

问题

我想为所有的测试用例编写使用共享上下文(共享状态)的集成测试。

来自文档

> 当使用类夹具时,xUnit.net 将确保在运行任何测试之前创建夹具实例,一旦所有测试都完成,它将通过调用 Dispose(如果存在)来清理夹具对象。

从文档中可以看出,我需要使用 IClassFixture。好的,接下来。

我创建了一个带有控制器的示例 ASP .NET Core Web API,并在 Program.cs 中添加了唯一的一行:

public partial class Program { }

项目中没有其他更改。

然后我添加了 xUnit 测试项目,在其中我添加了对我的 Web API 项目的引用,并修改了默认的 UnitTest1 类,使用以下代码:

public class UnitTest1 : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    private string? _val;
    public UnitTest1(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public void Test1()
    {
        Assert.Null(_val);
        _val = "smth";
    }

    [Fact]
    public void Test2()
    {
        Assert.NotNull(_val);
    }
}

基本上,我想在 Test1 中设置“共享上下文”(在本例中是一个字符串变量),然后在 Test2 中使用它。我运行测试用例,看到 Test1 通过了,但 Test2 失败了。

我看过 https://stackoverflow.com/questions/61603612/xunit-iclassfixture-constructor-being-called-multiple-times 并尝试使用测试资源管理器窗口,甚至切换到 Rider,但没有帮助。有人遇到过这样的行为吗?

英文:

I want to write integration tests with shared context (shared state) for all testcases.

From docs:

> When using a class fixture, xUnit.net will ensure that the fixture instance will be created before any of the tests have run, and once all the tests have finished, it will clean up the fixture object by calling Dispose, if present.

It follows from docs that I need to use IClassFixture. Ok then.

I create sample ASP .NET Core Web API with controllers and in Program.cs add the only line:

public partial class Program { }

Nothing else is changed in a project.

Then I add xUnit test project where I add reference for my web api project and modify the default UnitTest1 class with the following code:

public class UnitTest1 : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    private string? _val;
    public UnitTest1(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public void Test1()
    {
        Assert.Null(_val);
        _val = "smth";
    }

    [Fact]
    public void Test2()
    {
        Assert.NotNull(_val);
    }
}

So basically I want to set "shared context" (which is a string variable in this case) in Test1 and use it in Test2. I run testcases and I see that Test1 passes and Test2 fails.

I have seen https://stackoverflow.com/questions/61603612/xunit-iclassfixture-constructor-being-called-multiple-times and tried using test explorer window or even switch to Rider but that did not help. Did someone encounter such a behavior?

答案1

得分: 2

这部分内容的翻译如下:

这部分代码是正确的,但你的实现方式有误。xUnit 运行时会为每个测试执行创建一个新的 UnitTest1 实例,但它应该仅为当前测试批次执行上下文的生命周期内创建一个 WebApplicationFactory<Program> 的单一实例,仅供这个测试类使用

  • 你的 _val 变量根本没有定义为测试夹具的一部分,所以在不同的测试之间值不会保留下来是合理的。

因为你传递的是工厂而不是实例,所以你会多次调用 factory.CreateClient();,这是预期的。在这种情况下,通常不会将工厂用作测试夹具,但是你的测试夹具可以在内部使用工厂方法:

/// <summary>在相同上下文中分享给多个测试的夹具</summary>
public class MyTestFixture : IDisposable
{
    public HttpClient Client { get; private set; }
    public string? Value { get; set; }
    
    public MyTestFixture(WebApplicationFactory<Program> factory)
    {
        Client = factory.CreateClient();
    }

    public void Dispose()
    {
        // 清理任何非托管引用
    }
}

* 如果你没有使用 DI 来创建工厂,那么你应该在构造函数中直接实例化工厂,而不是将它作为参数传递。

public class UnitTest1 : IClassFixture<MyTestFixture>
{
    private readonly MyTestFixture _sharedContext;

    public UnitTest1(MyTestFixture testFixture)
    {
        _sharedContext = testFixture;
    }

    [Fact]
    public void Test1()
    {
        Assert.Null(_sharedContext.Value);
        _sharedContext.Value = "smth";
    }

    [Fact]
    public void Test2()
    {
        Assert.NotNull(_sharedContext.Value);
    }
}
英文:

This is working correctly, but you have implemented it wrong. xUnit runtime will create a new instance of UnitTest1 for every test execution, but it should only create a single instance of WebApplicationFactory&lt;Program&gt; for the lifetime of the current test batch execution context for this test class.

  • Your _val variable is not defined as part of the test fixture at all, so that makes sense that the value is not persisted across the different tests.

Because you are passing the factory, and not the instance, you will experience multiple calls to factory.CreateClient(); and this is expected. In this scenario you wouldn't normally use a factory as the test fixture, but your test fixture could use the factory method internally:

/// &lt;summary&gt;Fixture to share across many tests in the same context&lt;/summary&gt;
public class MyTestFixture : IDisposable
{
    public HttpClient Client { get; private set; }
    public string? Value { get; set; }
    
    public MyTestFixture(WebApplicationFactory&lt;Program&gt; factory)
    {
        Client = factory.CreateClient();
    }

    public void Dispose()
    {
        // clean up any unmanaged references
    }
}

* if you are not using DI for your factory, then you should instantiate the factory directly in the constructor instead of expecting it as an argument.

public class UnitTest1 : IClassFixture&lt;MyTestFixture&gt;
{
    private readonly MyTestFixture _sharedContext;

    public UnitTest1(MyTestFixture testFixture)
    {
        _sharedContext = testFixture;
    }

    [Fact]
    public void Test1()
    {
        Assert.Null(_sharedContext.Value);
        _sharedContext.Value = &quot;smth&quot;;
    }

    [Fact]
    public void Test2()
    {
        Assert.NotNull(_sharedContext.Value);
    }
}

huangapple
  • 本文由 发表于 2023年6月1日 20:11:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76381726.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定