2

I have to make a program that basically reads x lines from a file and then tries to find it in the db then take some of the info in the file and update the info of the row in the db.

If all else fails insert it as a new row in db.

So basically alot of inserting and updating.

I trying to unit test as I am updating a lot of fields I want to make sure that each piece of info is going to right spot(ie not email address is being set with firstname or something like that).

However I am not sure how to do it as I see this being the program method

// get records from file

foreach record in file
{
   db record = find if it is in db

   if(record != null)
   {
        if(do another logic check)
         {
             // update record
         }
         else if(do another logic check)
         { 
            // update record
         }
        else
        {
           // do some more logic 

            if(do another check)
             {
               // update record
             }
        }
   }
   else
   {
      // do some more logic checks and do inserts.
   }
}

I see this in a void method with maybe some private methods(for say update record part). Now how should I be unit testing this? I want to unit test the first if(do another logic check) and if say some record I send in meets those conditions.

However since those would be private methods I can't unit test them, right now I don't see the the method returning anything since it will go through hundreds of records and I probably will be printing out most of stuff into a error log file or something.

the code I showed you is in a service layer. I would mock out the db method calls with moq.

The application is a console app.

Any suggestions how I could better break this up so I could check the logic?

chobo2
  • 83,322
  • 195
  • 530
  • 832

4 Answers4

2

You could mock db calls, also you could mock record type and pass mocked inside method

Zaranitos
  • 101
  • 3
  • Already do that but not sure how that would in the end help me to figure out what path my code took. – chobo2 Oct 04 '12 at 16:49
0

The code I showed you is in a service layer. I would mock out the db method calls with moq.

That's the key. You should abstract all database dependencies behind an interface that you could mock in your unit test. You would define expectations on the methods. As far as reading the file is concerned, you should abstract the method so that it doesn't take a filename but a Stream or StreamReader instead which would allow you to feed a sample file data in the unit test instead of relying on the existence of an actual file.

So your class could now become:

public class SomeClass
{
    private readonly IRepository _repo;
    public SomeClass(IRepository repo)
    {
        _repo = repo;
    }

    public void SomeMethod(StreamReader reader)
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            // put your logic and use the _repo to update your database.
        }
    }
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • What do you mean "expectations on the method"? All of my database calls are in a repo with a interface so ya they will be mocked up. Like I said just not sure how to figure out the unit test A takes the right path it should take. – chobo2 Oct 04 '12 at 16:21
  • Have you used Moq? The [documentation](http://code.google.com/p/moq/wiki/QuickStart) illustrates how you could define an expectation for methods using the `.Setup()` call on the mock. – Darin Dimitrov Oct 04 '12 at 16:25
  • I have and I am using them but the problem is like I did a verify on the update method to see if it was being called what is good for now but the next part of the logic will also eventually call and update so can't use that anymore. – chobo2 Oct 04 '12 at 16:27
  • Why would the next part of the logic will eventually call? On what does the condition is based upon? You should be able to mock everything in order to control the flow of your program. – Darin Dimitrov Oct 04 '12 at 16:28
  • If you look at the if statements they all update record. It is just I have an order of what to look at first try to find value in db, next try to find it from file and then finally try some other db table. – chobo2 Oct 04 '12 at 16:30
  • You could check the order of calls when verifying: http://stackoverflow.com/a/3223698/29407 – Darin Dimitrov Oct 04 '12 at 16:32
  • Won't I still run into the problem that all the methods are private or are you talking about using the repo ones? – chobo2 Oct 04 '12 at 16:36
  • You should not test your private methods. Only the public method. – Darin Dimitrov Oct 04 '12 at 16:37
  • As I said that's why this is making it very hard for me to figure out how to get a result back to test since it is one method with lots of private methods(many of which get called in this loop) – chobo2 Oct 04 '12 at 16:42
  • But you should test only if the right methods are called on your repository in the correct order. Ignore your private methods in the test. – Darin Dimitrov Oct 04 '12 at 16:44
  • But if you look at my example would not updateRecord() always be the first repo method call in all examples? – chobo2 Oct 04 '12 at 16:50
  • No, this will depend on the flow you need to test. For example you need to test that if the record is null you are not updating anything and that the updateRecord method is never called actually. Also once the record is not null you seem to be updating only if certain conditions are met. You must have a separate unit test for each of those conditions and assert that the update method is called with the right arguments. – Darin Dimitrov Oct 04 '12 at 16:53
  • Well the thing is the arguments would be the same for the update method. Basically most of the arguments is for the where clause all expect one. I am looking for one single piece of data(lets say a phone number). I first look for the record in the db and see if a phone # is set, If it is set I add the area code to it, If not I look at the next area the phone number might be, finally I give it some default area code. In the end the I will be sending to the update member a phone # combined with an area code. – chobo2 Oct 04 '12 at 16:57
  • If the arguments are exactly the same for the update method why do you care which one is called? What is important in this case is that it is called the right number of times. I think we are getting in a very extensive discussion and I am afraid we won't get very far like this. Maybe you could show your code so that we could talk something concrete? – Darin Dimitrov Oct 04 '12 at 17:04
0

Your class may end up looking like this from a high level:

// get records from file

foreach record in file
{
   dbRecord = _db.Find(record)

   if(dbRecord != null)
   {
        if(logicCheck(dbRecord))
        {
            _db.Update(dbRecord)
        }
        else if(logicCheck2(dbRecord))
        { 
            _db.Update3(dbRecord)
        }
        else
        {
            // do some more logic 
        }
        if(otherCheck(dbRecord))
        {
            _db.Update2(dbRecord)
        }
   }
   else
   {
      // do some more logic checks
      _db.Insert(record)
   }
}

The tests would look something like this:

public void TestInsert()
{
    _mock.Setup(r => r.Find(record).Returns(null))

    class = new class(_mock.Object);
    class.methodToTest(fileRecord);

    _mock.Verify( r => r.Insert(record));
}

public void TestUpdate()
{
    var dbRecordThatPassesLogicCheck = new dbRecord(// initialize for test)
    _mock.Setup(db => db.Find(record).Returns(dbRecordThatPassesLogicCheck))

    class = new class(_mock.Object);
    class.methodToTest(fileRecord);

    _mock.Verify( r => r.Update(dbRecordThatPassesLogicCheck));
}

public void TestUpdate3()
{
    var dbRecordThatPassesLogicCheck2 = new dbRecord(// initialize for test)
    _mock.Setup(db => db.Find(record).Returns(dbRecordThatPassesLogicCheck2))

    class = new class(_mock.Object);
    class.methodToTest(fileRecord);

    _mock.Verify( db => db.Update3(dbRecordThatPassesLogicCheck2));
}

What Moq (or any other mocking framework) buys you here is the ability to simulate that the class calls Update if logicCheck is true, or calls Update3 if logicCheck2 passes but logicCheck1 does not. You don't mock out your private methods, you exercise them with preset input coming from the find method (whose repository you are mocking).

Your class should probably take a second interface dependency on the retrieval of the initial record get which will make the code more decoupled (now if file system read changes to web service read the class isn't directly affected) and allow you more testability.

Phil Patterson
  • 1,242
  • 15
  • 25
  • Well they are all the same update method. I think I got a way to do it from your example though. Basically I can change the data from each unit test and then use the verify to make sure that data went through the right call. – chobo2 Oct 04 '12 at 19:01
  • I've done this exact same thing when implementing a custom membership provider. – Phil Patterson Oct 15 '12 at 17:23
0

We tend to use lots of small classes with injected dependencies. This provides usability and testability seams. We also tend to extract if .. else if .. else blocks into injectable Strategy objects - which makes their logic testable outside of the process that uses them.

This separates the concerns for reading the file

class RecordFileReader
{
    void UpdateAll(file)
    {
        foreach (var record in file)
        {
            _fileRecordImporter.Import(record)
        }
    }
}

from processing a single file record

class FileRecordImporter
{   
    void Import(record)
    {
        db_record = find if it is in db
        if (db_record != null)
        {
            _dbRecordUpdater.Update(db_record, record)
            return;
        }
        _dbRecordCreator.CreateFrom(record);
    }
}

and determining how to transfer information from the record to the db for updates

class DbRecordUpdater
{
    void Update(db_record, record)
    {
        var recordUpdater = _recordUpdaters.FirstOrDefault(x=>x.IsMatch(db_record, record));
        if (recordUpdater != null)
        {
            recordUpdater.Update(db_record, record)
        }
    }
}

and inserts

class DbRecordCreator
{
    void CreateFrom(record)
    {
        var recordCreator = _recordCreators.FirstOrDefault(x=>x.IsMatch(record));
        if (recordCreator != null)
        {
            var db_record = recordCreator.Create(record)
            ...
        }
    }
}
Handcraftsman
  • 6,863
  • 2
  • 40
  • 33