我正确地编写单元测试吗? NUnit + NSubstitute

时间:2015-01-17 00:11:53

标签: c# unit-testing mocking nunit nsubstitute

我最近开始学习如何编写单元测试,以及测试功能和模拟内容的单元的哪个部分。我正在使用NSubstitute作为我的模拟框架。我的示例基本上调用了一个repo类,然后对外部服务进行WEB API Web调用,即:AddCreditCard,然后返回结果。我为AddCreditCard创建了2个单元测试,一个用于Success,另一个用于Fail。我仍然不能100%确定我正在做所有这些。单元测试正在通过,但我不确定我的Asserts是否在正确的数据上完成...欢迎所有帮助和建议!

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);
    }

1 个答案:

答案 0 :(得分:2)

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

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

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

我要说的第二件事是你缺少一个或多个测试(这个数字有点风格)。就目前而言,您没有验证提供给ConfigureSettings替代品上的PostsignedRequest来电的信息。您的存储库代码可能只是_signedRequest.Post("some random string");,您的测试仍然会通过。我将添加另一个验证这些调用的测试,以确保实际正确发送请求。这是Received验证有意义的地方。类似的东西:

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