简体   繁体   中英

mocking lambda expressions that are used in methods of the dut

I have a method like below that I want to unit test:

public string GetReferentie(string prefix)
{
        IRepositoryAsync<ParameterGetal> parameterGetalRepository = _unitOfWork.RepositoryAsync<ParameterGetal>();
        var dateparameterGetal = parameterGetalRepository
            .Query(o => o.parameter=="Datum")
            .Select()
            .Single();
        var ordertellerparametergetal = parameterGetalRepository
                .Query(o => o.parameter == "orderteller")
                .Select()
                .Single();
        DateTime date = DateTime.Parse(dateparameterGetal.parameterwaarde);
        int orderteller=0;
        if (date == DateTime.Today)
        {
            orderteller = int.Parse(ordertellerparametergetal.parameterwaarde); 
        }
        else
        {
            dateparameterGetal.parameterwaarde = string.Format("{0:dd/MM/yyyy}", DateTime.Today);
            orderteller = 0;
        }
        orderteller++;

        ordertellerparametergetal.parameterwaarde = orderteller.ToString();

        string result = string.Format("{0}{1:yyyyMMdd}.{2:00}",prefix,DateTime.Today,orderteller);

        return result;
}

The thing here is that i am using 2 lambda expressions and that makes it difficult to mock since most solutions i found in stackoverflow work out a solution that is based on ignoring the lambda that is being used.

How do you go about this ? Note, i have a solution. I will post it as an answer next.

Sometimes for unit testing one has to be pragmatic in the design of the DUT and perhaps add something to the class that is only used in the unit test. I adapted the class from above to make the lambda expressions variabled and allow access to those expressions via a getter. This way the unit test can access the getter, and hence the mocker can really check which lambda is being passed. Enough talk here is the code:

public class ReferentieBuilder : IReferentieBuilder
{
    private readonly IUnitOfWorkAsync _unitOfWork;
    private static readonly Expression<Func<ParameterGetal, bool>> _datumExpression = o => o.parameter=="Datum";
    private static readonly Expression<Func<ParameterGetal, bool>> _ordertellerExpression = o => o.parameter == "orderteller";

    public ReferentieBuilder(IUnitOfWorkAsync unitOfWork)
    {
        if (unitOfWork == null)
        {
            throw new ArgumentNullException("unitOfWork");
        }
        _unitOfWork = unitOfWork;
    }

    public static Expression<Func<ParameterGetal, bool>> DatumExpression
    {
        get { return _datumExpression;}
    }

    public static Expression<Func<ParameterGetal, bool>> OrderTellerExpression
    {
        get { return _ordertellerExpression; }
    }

    public string GetReferentie(string prefix)
    {
        IRepositoryAsync<ParameterGetal> parameterGetalRepository = _unitOfWork.RepositoryAsync<ParameterGetal>();
        Debug.Assert(parameterGetalRepository!=null);
        var dateparameterGetal = parameterGetalRepository
            .Query(DatumExpression)
            .Select()
            .Single();
        var ordertellerparametergetal = parameterGetalRepository
                .Query(OrderTellerExpression)
                .Select()
                .Single();
        DateTime date = DateTime.Parse(dateparameterGetal.parameterwaarde);
        int orderteller=0;
        if (date == DateTime.Today)
        {
            orderteller = int.Parse(ordertellerparametergetal.parameterwaarde); 
        }
        else
        {
            dateparameterGetal.parameterwaarde = string.Format("{0:dd/MM/yyyy}", DateTime.Today);
            orderteller = 0;
        }
        orderteller++;

        ordertellerparametergetal.parameterwaarde = orderteller.ToString();

        string result = string.Format("{0}{1:yyyyMMdd}.{2:00}",prefix,DateTime.Today,orderteller);

        return result;
    }
}

see I hadded two static readonly member variables and foreseen static readonly properties as well. DatumExpression and OrderTellerExpression.

The unit test code becomes then something like this :

[TestFixture]
class ReferentieBuilderTests
{
    private IUnitOfWorkAsync _unitOfWork;

    [SetUp]
    public void setup()
    {
        List<ParameterGetal> dateParameterGetallen = new List<ParameterGetal>
        {
            new ParameterGetal{ID = 29, parameter = "Datum", parameterwaarde = string.Format("{0:dd/MM/yyyy}", DateTime.Today.AddDays(-1)) }
        };

        List<ParameterGetal> tellerParameterGetallen = new List<ParameterGetal>
        {
            new ParameterGetal{ID = 3, parameter = "orderteller", parameterwaarde = "4" }
        };

        IQueryFluent<ParameterGetal> datefluent = MockRepository.GenerateStub<IQueryFluent<ParameterGetal>>();
        IQueryFluent<ParameterGetal> tellerfluent = MockRepository.GenerateStub<IQueryFluent<ParameterGetal>>();
        IRepositoryAsync<ParameterGetal> parametergetalrepository = MockRepository.GenerateStub<IRepositoryAsync<ParameterGetal>>();
        _unitOfWork = MockRepository.GenerateStub<IUnitOfWorkAsync>();

        _unitOfWork.Stub(u => u.RepositoryAsync<ParameterGetal>())
            .Return(parametergetalrepository);
        parametergetalrepository.Stub(r => r.Query(ReferentieBuilder.DatumExpression))
            .Return(datefluent);
        parametergetalrepository.Stub(r => r.Query(ReferentieBuilder.OrderTellerExpression))
            .Return(tellerfluent);
        datefluent.Stub(q => q.Select())
            .Return(dateParameterGetallen);
        tellerfluent.Stub(q => q.Select())
            .Return(tellerParameterGetallen);
    }

    [Test]
    public void GetFirstReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.01", prefix, today);
        Assert.AreEqual(correctReferentie,referentieBuilder.GetReferentie(prefix), "Wrong First Referentie");
    }

    [Test]
    public void GetSecondReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        referentieBuilder.GetReferentie(prefix);
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.02", prefix, today);
        Assert.AreEqual(correctReferentie,referentieBuilder.GetReferentie(prefix), "Wrong Second Referentie");
    }

    [Test]
    public void GetThirdReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        referentieBuilder.GetReferentie(prefix);
        referentieBuilder.GetReferentie(prefix);
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.03", prefix, today);
        Assert.AreEqual(correctReferentie, referentieBuilder.GetReferentie(prefix), "Wrong Second Referentie");
    }
}

The trick here is that the mocks is using ReferentieBuilder.DatumExpression and ReferentieBuilder.OrderTellerExpression and not passing in the same lambda expression in a hardcoded way in the unit test. This has an extra advantage that there is no duplication. The code is not that much extra complicated by adding the readonly properties for the lambda expressions that allow the unit test to access.

Please don't judge the logic of the code too much. It is trying to generate a running number that starts at 1 every new day. The last time the function was accessed is stored in the database, and the last number as well. This is as per requirement and i don't like it either.

Using Eugene comment following solution is also possible :

[TestFixture]
class ReferentieBuilderTests
{
    private IUnitOfWorkAsync _unitOfWork;

    [SetUp]
    public void setup()
    {
        List<ParameterGetal> dateParameterGetallen = new List<ParameterGetal>
        {
            new ParameterGetal{ID = 29, parameter = "Datum", parameterwaarde = string.Format("{0:dd/MM/yyyy}", DateTime.Today.AddDays(-1)) }
        };

        List<ParameterGetal> tellerParameterGetallen = new List<ParameterGetal>
        {
            new ParameterGetal{ID = 3, parameter = "orderteller", parameterwaarde = "4" }
        };

        IQueryFluent<ParameterGetal> datefluent = MockRepository.GenerateStub<IQueryFluent<ParameterGetal>>();
        IQueryFluent<ParameterGetal> tellerfluent = MockRepository.GenerateStub<IQueryFluent<ParameterGetal>>();
        IRepositoryAsync<ParameterGetal> parametergetalrepository = MockRepository.GenerateStub<IRepositoryAsync<ParameterGetal>>();
        _unitOfWork = MockRepository.GenerateStub<IUnitOfWorkAsync>();

        _unitOfWork.Stub(u => u.RepositoryAsync<ParameterGetal>())
            .Return(parametergetalrepository);
        parametergetalrepository.Stub(r => r.Query(Arg<Expression<Func<ParameterGetal, bool>>>.Matches(a => LambdaCompare.Eq(a, o => o.parameter == "Datum"))))
            .Return(datefluent);
        parametergetalrepository.Stub(r => r.Query(Arg<Expression<Func<ParameterGetal, bool>>>.Matches(a => LambdaCompare.Eq(a, o => o.parameter == "orderteller"))))
            .Return(tellerfluent);
        datefluent.Stub(q => q.Select())
            .Return(dateParameterGetallen);
        tellerfluent.Stub(q => q.Select())
            .Return(tellerParameterGetallen);
    }

    [Test]
    public void GetFirstReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.01", prefix, today);
        Assert.AreEqual(correctReferentie,referentieBuilder.GetReferentie(prefix), "Wrong First Referentie");
    }

    [Test]
    public void GetSecondReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        referentieBuilder.GetReferentie(prefix);
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.02", prefix, today);
        Assert.AreEqual(correctReferentie,referentieBuilder.GetReferentie(prefix), "Wrong Second Referentie");
    }

    [Test]
    public void GetThirdReferentieOfDay_returnsCorrectReferentie()
    {
        ReferentieBuilder referentieBuilder = new ReferentieBuilder(_unitOfWork);

        string prefix = "P";
        referentieBuilder.GetReferentie(prefix);
        referentieBuilder.GetReferentie(prefix);
        DateTime today = DateTime.Today;
        string correctReferentie = string.Format("{0}{1:yyyyMMdd}.03", prefix, today);
        Assert.AreEqual(correctReferentie, referentieBuilder.GetReferentie(prefix), "Wrong Second Referentie");
    }
}

the lambda expression comparer can be found here : Most efficient way to test equality of lambda expressions

use the update of the link. Using this solution, no updates on the DUT are needed, ie no expression readonly properties are required.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM