haosmark haosmark - 4 months ago 10
C# Question

mstest how do I create test methods that work on the same object (cumulatively)?

I'm currently trying to implement testing for a calculator that I'm building (using composite pattern.) The first method should add $75, which works fine, however when the second method runs, "service" is reset and has $0 as job cost. If I combine both methods into one, then everything works as I expect it to.
How do I retain value in service field?

[TestClass()]
public class JobTests
{
private Service service;
private LaborTime laborTime;
private LaborRates laborRates;

[TestInitialize]
public void init()
{
service = new EmergencyService();
}

[TestMethod()]
// add one hour of service at $75/50 rate
public void Job_OnFullCost_Is75()
{
// Arrange
laborTime = new LaborTime(
checkIn: new DateTime(year: 2016, month: 7, day: 20, hour: 10, minute: 0, second: 0),
checkOut: new DateTime(year: 2016, month: 7, day: 20, hour: 11, minute: 0, second: 0)
);
laborRates = new LaborRates(75, 50);
service = new Labor(service, laborTime, laborRates);

// Act
var expected = 75.0M;
var actual = service.JobCost;

// Assert
Assert.AreEqual(expected, actual);
}

[TestMethod()]
// add another hour to the service, at same rate of $175/60
public void Job_OnFullCost_Is125()
{
// Arrange
laborTime = new LaborTime(
checkIn: new DateTime(year: 2016, month: 7, day: 20, hour: 12, minute: 0, second: 0),
checkOut: new DateTime(year: 2016, month: 7, day: 20, hour: 13, minute: 0, second: 0)
);
LaborRates laborRates = new LaborRates(75, 50);
service = new Labor(service, laborTime, laborRates);
//service.IsContinuation = true;

// Act
var expected = 125.0M;
var actual = service.JobCost;

// Assert
Assert.AreEqual(expected, actual);
}
}

Answer

Since unit tests are not guaranteed to all be run together, or to be run in a specific order, it's best practice to avoid adding a temporal dependency between them (i.e. requiring them to be run in a specific order in order to work right).

Instead, extract out the "hard parts" of adding labor into a separate helper method, and make your two methods test specific scenarios.

[TestClass()]
public class JobTests
{
    private Service service;

    [TestInitialize]
    public void init()
    {
        service = new EmergencyService();
    }

    [TestMethod()]
    // add one hour of service at $75/50 rate
    public void Job_OnFullCost_Is75()
    {
        // Arrange
        AddHourOfService(75, 50);

        // Act
        var expected = 75.0M;
        var actual = service.JobCost;

        // Assert
        Assert.AreEqual(expected, actual);
    }

    [TestMethod()]
    // add another hour to the service, at same rate of $175/60
    public void Job_OnFullCost_Is125()
    {
        // Arrange
        AddHourOfService(75, 50);
        AddHourOfService(75, 50);

        // Act
        var expected = 125.0M;
        var actual = service.JobCost;

        // Assert
        Assert.AreEqual(expected, actual);
    }

    private void AddHourOfService(int cost, int time)
    {
        var laborTime = new LaborTime(
        checkIn: new DateTime(year: 2016, month: 7, day: 20, hour: 10, minute: 0, second: 0),
        checkOut: new DateTime(year: 2016, month: 7, day: 20, hour: 11, minute: 0, second: 0)
    );
        var laborRates = new LaborRates(75, 50);
        service = new Labor(service, laborTime, laborRates);
    }
}

Besides removing temporal coupling between tests, this also has the nice side-effect of making the tests' purpose obvious straight from the code. You no longer need comments like "add one hour of service at $75/50 rate" because AddHourOfService(75, 50) makes it pretty obvious that's what happens. Letting code self-document this way is good because it's too easy for comments to get out of sync with your code (as you can see by your second comment which says "$175/60" when that's clearly not what the test is doing).