我是否正确编写了单元测试? NUnit + NSubstitute

Am I writing my unit tests correctly? NUnit + NSubstitute

我最近开始学习如何编写单元测试,以及要测试单元的哪一部分的功能以及模拟什么。我正在使用 NSubstitute 作为我的模拟框架。我的示例基本上调用了一个 repo class,然后它对外部服务进行 WEB API web 调用,即:AddCreditCard,然后 returns 结果。我为 AddCreditCard 创建了 2 个单元测试,一个用于成功,一个用于失败。我仍然不能 100% 确定我是否正确地完成了所有这些工作。单元测试通过了,但我不确定我的断言是否在正确的数据上完成...欢迎所有帮助和建议!

public interface IApiResponse<T>
{
    HttpStatusCode StatusCode { get; set; }
    T Result { get; set; }
    string ErrorMessage { get; }
    bool HasErrors { get; }
}

public interface ISignedRequest
{
    void ConfigureSettings(SignedRequestSettings settings);

    IApiResponse Post(string jsonData, Dictionary<string, string> parameters = null,
        IOptionalHeaders optionalHeaders = null);
}


public class PaymentRepository
{
    private readonly SignedRequestSettings _settings;
    private readonly ISignedRequest _signedRequest;

    public PaymentRepository(ISignedRequest signedRequest = null)
    {
        if (signedRequest == null)
            _signedRequest = new SignedRequest();
        else
            _signedRequest = signedRequest;
    }

    public IApiResponse AddCreditCard(CreditCard request)
    {
        var jsonData =
            JsonConvert.SerializeObject(request);

        string action = string.Format("customers/{0}/paymentmethods", request.ConnectId);
        _settings.Url = string.Format("{0}{1}", String.Empty, action);
        _signedRequest.ConfigureSettings(_settings);

        return _signedRequest.Post(jsonData);
    }
 }

    [Test]
    public void AddCreditCard_GivenValidCreditCard_ReturnsCreatedResult()
    {
        //Given
        var apiResponse = Substitute.For<IApiResponse>();
        apiResponse.StatusCode = HttpStatusCode.Created;

        var signedRequest = Substitute.For<ISignedRequest>();
        signedRequest.Post(Arg.Any<String>()).Returns(apiResponse);

        var creditCard = Substitute.For<CreditCard>();
        creditCard.ConnectId = Guid.Parse("1fc1ad83-cd4e-4b68-bce6-e03ee8f47fb6");

        var repo = new PaymentRepository(signedRequest);

        //When
        var addCreditCardResponse = repo.AddCreditCard(creditCard);

        //Then
        signedRequest.Received(1).ConfigureSettings(Arg.Any<SignedRequestSettings>());
        signedRequest.Received(1).Post(Arg.Any<String>());

        Assert.AreEqual(HttpStatusCode.Created, addCreditCardResponse.StatusCode);
    }

[Test]
    public void AddCreditCard_GivenInvalidCreditCard_ReturnsHasErrorsResult()
    {
        //Given
        var apiResponse = Substitute.For<IApiResponse>();
        apiResponse.HasErrors.Returns(true);
        apiResponse.ErrorMessage.Returns("Add credit card error message");

        var signedRequest = Substitute.For<ISignedRequest>();
        signedRequest.Post(Arg.Any<String>()).Returns(apiResponse);

        var creditCard = Substitute.For<CreditCard>();
        creditCard.ConnectId = Guid.Parse("1fc1ad83-cd4e-4b68-bce6-e03ee8f47fb6");

        var repo = new PaymentRepository(signedRequest);

        //When
        var addCreditCardResponse = repo.AddCreditCard(creditCard);

        //Then
        signedRequest.Received(1).ConfigureSettings(Arg.Any<SignedRequestSettings>());
        signedRequest.Received(1).Post(Arg.Any<String>());

        Assert.AreEqual(apiResponse.HasErrors, addCreditCardResponse.HasErrors);
        Assert.AreEqual(apiResponse.ErrorMessage, addCreditCardResponse.ErrorMessage);
    }

我认为您的测试基本没问题,但有些地方我会质疑。你的两个测试都在底部有这些行:

signedRequest.Received(1).ConfigureSettings(Arg.Any<SignedRequestSettings>());
signedRequest.Received(1).Post(Arg.Any<String>());

在您的 signedRequest 替代品上调用这些方法对那些测试真的很重要吗?我建议它可能不是。如果没有进行这些调用,我认为您的测试无论如何都会失败(尽管这在某种程度上是一种风格决定)。

我要说的第二件事是您错过了一项或多项测试(同样,这个数字有点风格化)。就目前而言,您没有验证提供给 ConfigureSettingsPost 调用的 signedRequest 替代信息。您的存储库代码可以简单地执行 _signedRequest.Post("some random string"); 而您的测试仍然会通过。我会添加另一个测试来验证这些调用,以确保请求确实正确发送。这是 Received 验证有意义的地方。类似于:

signedRequest.Received(1).ConfigureSettings(Arg.Is<SignedRequestSettings>(x=>x.Url == someHardCodedUrl));
signedRequest.Received(1).Post(someHardCodedJsonString);