How-To: Running Custom Code With DoMemberFunctionInstead Clauses Using Isolator++

Isolator++ APIs make it easy to return custom values or objects. It’s really easy to return values or control behavior of one or more methods. But sometimes you want more than that: You want to run your own code when a method gets called. You want to call a supplied method, change a static flag’s status, or even more complex stuff. To do so, we’ll use the DoMemberFunctionInstead clause.

Let’s look at our Person object:

int Person::CountMovesToUS(Address** addresses)

{

      int count = 0;

 

      for (int i=0; i<3; i++)

      {

            char* country = addresses[i]->GetCountry();

            if (strcmp (country,"US")==0)

            {

                  count++;

            }

      }

      return count;

}

 

The method counts the number of times the person moved to US. It does so by iterating through Address pointers in an array. The problem is that GetCountry method throws an exception:

 

char* GetCountry()

{

     throw ("Not implemented yet!");

}

 

I want to simulate two travels to the US and one to Canada to test that counting is done correctly. One way I can do it is like this:

 

TEST_F(PersonTests, CountMovesToUS_2Moves_Returns2)

{

    Address** addresses= new Address*[3];

 

    addresses[0] = FAKE<Address>();

    addresses[1] = FAKE<Address>();

    addresses[2] = FAKE<Address>();

 

    WHEN_CALLED(addresses[0]->GetCountry()).ReturnPtr("US");

    WHEN_CALLED(addresses[1]->GetCountry()).ReturnPtr("US");

    WHEN_CALLED(addresses[2]->GetCountry()).ReturnPtr("Canada");

 

    Person person;

 

    ASSERT_EQ(2,person.CountMovesToUS(addresses));

    ISOLATOR_CLEANUP();

}

 

That’s the straight forward way – setting up 3 different FAKE objects, and then setting up the behavior for every call. But let’s do it in a more clever way. Let’s define a class that helps select countries:

 

class CountrySelector

{

public:

      int count;

 

      CountrySelector()

      {

            count=0;

      }

 

      char* USTwice()

      {

            count++;

            if (count>2)

                  return "Canada";

            return "US";

      }

};

For this example, I’ve selected to implement USTwice as the method that implements the order I need in my test. Obviously, if I have multiple scenarios, it would be wise to add those to this class as well. Now let’s use it in a test:

    1 TEST_F(PersonTests, CountMovesToUS2_2Moves_Returns2)

    2 {

    3     Address** addresses= new Address*[3];

    4 

    5     Address* fakeAddress = FAKE<Address>();

    6     addresses[0] = fakeAddress;

    7     addresses[1] = fakeAddress;

    8     addresses[2] = fakeAddress;

    9 

   10     CountrySelector selector;

   11 

   12     WHEN_CALLED(fakeAddress->GetCountry())

   13         .DoMemberFunctionInstead(&selector,USTwice) ;

   14 

   15     Person person;

   16 

   17     ASSERT_EQ(2,person.CountMovesToUS(addresses));

   18     ISOLATOR_CLEANUP();

   19 }

  • This time, I’m using a single fake object, defined in line 5. I’m using the same instance for all addresses.
  • In line 12, I’m using the WHEN_CALLED macro, but this time…
  • In line 13 I’m using DoMemberFunctionInstead. It takes a reference to the CountrySelector and the method it needs to invoke when the method runs. This calls the method USTwice whenever GetCountry gets called.
  • In line 18 we clean up everything.

What happens is that the method USTwice gets called three times – twice returning “US” and in the last time it returns “Canada”, thanks to our smart counting mechanism.

Along with DoMemberFunctionInstead, there’s another macro: DoStaticOrGlobalInstead that calls a static or a global method, rather than an instance method. I’ll describe this in another post. Both macros are the ultimate behavior setters – they let you define whatever code you want to run, when you decide to.

Gil Zilberfeld