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