chevron-thin-right chevron-thin-left brand cancel-circle search youtube-icon google-plus-icon linkedin-icon facebook-icon twitter-icon toolbox download check linkedin phone twitter-old google-plus facebook profile-male chat calendar profile-male
Welcome to Typemock Answers. Here you can ask and receive answers from other community members. And if you liked or disliked an answer or thread: react with an up- or downvote Enjoy!

How to mock/fake public property value conditionally

+1 vote

Hi, I have following code under test

        public class PlanController
        {
            public void ChangeStatus(int planId)
            {
                var plan = Plan.Load(planId);

                if (plan.Status != PlanStatus.InProgress)
                {
                    throw new NotSupportedException();
                }

                plan.Status = PlanStatus.Locked;
                plan.Update();

                PlanChanges.SaveChanges(plan.Id, plan.Status);
            }
        }

and Test for the ChangeStatus looks like this

        public class PlanControllerTest
        {
            public void ChangeStatus_WhenCalled_StatusIsChangedToLocked()
            {
                // ARRANGE

                const int planId = 1;

                var fakePlan = Isolate.Fake.Instance<Plan>();
                Isolate.WhenCalled(() => fakePlan.Status).WillReturn(PlanStatus.InProgress);

                Isolate.WhenCalled(() => Plan.Load(planId)).WithExactArguments().WillReturn(fakePlan);

                var target = new PlanController();

                // ACT

                target.ChangeStatus(planId);

                // ASSERT

                Isolate.Verify.WasCalledWithExactArguments(() => fakePlan.Status = PlanStatus.Locked);

                Isolate.Verify.WasCalledWithExactArguments(() => fakePlan.Update());

                Isolate.Verify.WasCalledWithExactArguments(() => PlanChanges.SaveChanges(planId, PlanStatus.Locked));
            }
        }

Problem is that test is not strong enough, because if someone changes code under test like this

...
                plan.Status = PlanStatus.Locked;
                plan.Update();

                plan.Status = PlanStatus.InProgress;

                PlanChanges.SaveChanges(plan.Id, plan.Status);
...

the test will still pass, but the PlanChanges.SaveChanges(plan.Id, plan.Status) will be called with wrong status.

 

How to solve this situation? What is the "best practice" approach?

I was thinking about  changing the test to do some kind of condiotional mocking / faking of fakePlan.Status property, so the test would look like this

            public void ChangeStatus_WhenCalled_StatusIsChangedToLocked()
            {
                // ARRANGE

                const int planId = 1;

                var fakePlan = Isolate.Fake.Instance<Plan>();
                Isolate.WhenCalled(() => fakePlan.Status).WillReturn(PlanStatus.InProgress);

                // This is new
                Isolate.WhenCalled(() => { fakePlan.Status = PlanStatus.Locked; }).WithExactArguments().DoInstead((context) =>
                {
                    Isolate.WhenCalled(() => fakePlan.Status).WillReturn(PlanStatus.Locked);
                });

                Isolate.WhenCalled(() => Plan.Load(1)).WithExactArguments().WillReturn(fakePlan);

                var target = new PlanController();

                // ACT

                target.ChangeStatus(planId);

                // ASSERT

                // This is changed
                Assert.That(fakePlan.Status, Is.EqualTo(PlanStatus.Locked));

                Isolate.Verify.WasCalledWithExactArguments(() => fakePlan.Update());

                Isolate.Verify.WasCalledWithExactArguments(() => PlanChanges.SaveChanges(planId, PlanStatus.Locked));
            }

But this fails because on the last line of test 

Isolate.Verify.WasCalledWithExactArguments(() => PlanChanges.SaveChanges(planId, PlanStatus.Locked));

The SaveChanges is called with PlanStatus.InProgress, because it seems that fakePlan.Status is changed to PlanStatus.Locked only for test code and not for method code which is under test.

Thanks for help.

I am using Typemock for C#, v8.6.0.22 and Visual Studio Enterprise 2017, v15.5.6

asked Feb 28 by Ctvt (2,780 points)

1 Answer

+1 vote

Hey Milan,

Try writing your test like this:

public void ChangeStatus_WhenCalled_StatusIsChangedToLocked()
{
    // ARRANGE

    const int planId = 1;

    var fakePlan = Isolate.Fake.Instance<Plan>();
    Isolate.WhenCalled(() => fakePlan.Status).WillReturn(PlanStatus.InProgress);

    Isolate.WhenCalled(() => { fakePlan.Status = PlanStatus.InProgress; }).WithExactArguments().DoInstead((context) =>
    {
        fakePlan.Status = PlanStatus.Locked;
    });

    Isolate.WhenCalled(() => Plan.Load(1)).WithExactArguments().WillReturn(fakePlan);

    var target = new PlanController();

    // ACT

    target.ChangeStatus(planId);

    // ASSERT

    
    Assert.That(fakePlan.Status, Is.EqualTo(PlanStatus.Locked));

    Isolate.Verify.WasCalledWithExactArguments(() => fakePlan.Update());

    Isolate.Verify.WasCalledWithExactArguments(() => PlanChanges.SaveChanges(planId, PlanStatus.Locked));
}

Let me know if it helps.

Cheers,

Sapir.

answered Mar 1 by SapirTypemock (2,120 points)

Hi  Sapir,

thanks for reply. I tried suggested approach and test is failing on 

    Assert.That(fakePlan.Status, Is.EqualTo(PlanStatus.Locked));

because when creating the fakePlan

    ...
    var fakePlan = Isolate.Fake.Instance<Plan>();
    Isolate.WhenCalled(() => fakePlan.Status).WillReturn(PlanStatus.InProgress);
    ...

Status value is set by WhenCalled and then calling in code fakePlan.Status will always return PlanStatus.InProgress.

Do you have any other suggestion?

Hey Milan,

Can you send me the relevant parts of classes Plan, PlanChanges and PlanStatus?

Do you use Nunit?

Thanks,

Sapir.

Sure, here are relevant parts:

public enum PlanStatus
{
    InProgress = 1,
    Locked = 2
}

public class Plan
{
    public int Id { get; set; }

    public PlanStatus Status { get; set; }

    public static Plan Load(int id)
    {
        var dataContext = new CurrentInstanceDataContext(GlogalSettings.ConnectionStirng);

        var plan = dataContext.Plans.Where(plan => plan.Id == id).Single();

        return plan;
    }

    public void Update()
    {
        var dataContext = new CurrentInstanceDataContext(GlogalSettings.ConnectionStirng);

        var plan = dataContext.Plans.Where(plan => plan.Id == id).Single();
        plan.Status = this.Status;

        dataContext.SubmitChanges();
    }
}

public class PlanChanges
{
    public static void SaveChanges(int planId, PlanStatus status)
    {
        var dataContext = new CurrentInstanceDataContext(GlogalSettings.ConnectionStirng);

        var newChanges = new PlanChange
        {
            PlanId = planId,
            Status = status,
            CreatedDate = DateTime.Now,
            CreatedBy = CurrentSession.currentUserId
        };

        dataContext.PlanChanges.InsertOnSubmit(newChanges);
        dataContext.SubmitChanges();
    }
}

public class PlanController
{
    public void ChangeStatus(int planId)
    {
        var plan = Plan.Load(planId);

        if (plan.Status != PlanStatus.InProgress)
        {
            throw new NotSupportedException();
        }

        plan.Status = PlanStatus.Locked;
        plan.Update();

        PlanChanges.SaveChanges(plan.Id, plan.Status);
    }
}

 

Thanks, Milan, we're checking now.

Hi Milan,

Try this:

        public void TestMethod()
        {
            // ARRANGE

            const int planId = 1;

            var fakePlan = Isolate.Fake.Instance<Plan>();
            Isolate.WhenCalled(() => fakePlan.Status).WillReturn(PlanStatus.InProgress);

            // This is new
            Isolate.WhenCalled(() => { fakePlan.Status = PlanStatus.InProgress; }).WithExactArguments().DoInstead((context) =>
            {
                Isolate.WhenCalled(() => fakePlan.Status).WillReturn(PlanStatus.Locked);
            });

            Isolate.WhenCalled(() => Plan.Load(1)).WithExactArguments().WillReturn(fakePlan);

            var target = new PlanController();

            // ACT

            target.ChangeStatus(planId);

            // ASSERT


            Assert.That(fakePlan.Status, Is.EqualTo(PlanStatus.Locked));

            Isolate.Verify.WasCalledWithExactArguments(() => fakePlan.Update());

            Isolate.Verify.WasCalledWithExactArguments(() => PlanChanges.SaveChanges(planId, PlanStatus.Locked));
        }

 

...