CSC/ECE 517 Fall 2009/wiki1a 2 i7

From Expertiza_Wiki
Jump to navigation Jump to search

What are Mock Object Frameworks?

"Mock object frameworks" allow for the creation and use of "mock objects" which are used to mock existing roles in a system. Roles are identified in a system as an interface or abstract type where its responsibility is made aware through its definition. Mock objects are typically provided behavior and expectations programatically to allow for testing interactions with them.


Where mock objects fit in:

Why use Mock Object Frameworks?

Using mock objects allows for testing interaction with an interface rather than an implementation. There are a variety of reasons this may be beneficial, some of which may be when the mocked object is:

  • non-deterministic (i.e. time of day)
  • able to exhibit behavior that is difficult to reproduce directly (i.e. time out exception)
  • slow (i.e. accessing a database)


An example role in a system might be a DataProvider interface. Two implementation independent features of the getData() method of this interface may be that it returns a Data object and is able to throw a DataRetrievalException. An example definition is shown below in Java:

   interface DataProvider {
       Data getData() throws DataRetrievalException;
       boolean hasNext();
   }

A mock object in this case would be useful in a test which involves the interaction with a DataProvider, such as providing data to a DataConsumer class.


Example of a relationship between DataConsumers and DataProviders:


The implementation of the DataProvider is irrelevant to testing the correctness of a DataConsumer. The DataProvider interface provides a layer of abstraction to the DataConsumer, this clearly defines the contract that any implementation of a DataProvider must satisfy. Creating a barrier in this fashion and using a mock object allows for creating isolated unit tests in a complex system. The alternative would be setting up integration tests where an entire environment must be setup to test one particular component.

Mock DataProviders could be configured with behavior and expectations for each test case that the DataConsumer need be tested against. Some of the test cases may be when the DataProvider:

  • returns a faulty Data object
  • returns a legitimate Data object
  • returns null
  • throws a DataRetrievalException
  • throws an unchecked exception

Typical Steps to using a Mock Object Framework

Create the mock object

This step involves creation of the mock object.

An example in Mockito:

   PositionProvider mockPositionProvider = mock(PositionProvider.class);

This declares a variable called mockPositionProvider which is a mock object of the PositionProvider class. This variable is initialized to have the value mock(PositionProvider.class). The mock method is a call to the Mockito framework which tells it to return a mock object of the PositionProvider class.

Define the stubbed methods

This step involves defining what methods should do when they are called. This step can be combined with the next step which is defining expectations.

An example in Flex Mock:

   mockPositionProvider.should_receive(:getX).times(1).and_return(10);

This uses method chaining to declare that on the mockPositionProvider object the getX method should be called, the method should be called one time, and when called the method should return 10. An important feature to notice here is how natural using the mock object framework feels here, it should be noted that many mock object frameworks strive for this type of readability when designing their APIs. Reading from left to right you can easily understand what is going on, "the mockPositionProvider object should receive getX one time and return 10".

Define the expectations

This step involves defining what is expected to happen to the mock object when the test code is run such as what methods should be called, how many times should the methods be called, or what exceptions should be thrown.

An example in EasyMock:

   mockPositionProvider.getX()

This is plainly calling a method on a mocked object. In EasyMock, this is how expectations are created. Test code would be run at some point after this method call and then when the mock object is verified it will check if mockPositionProvider.getX() was indeed called through the test code.

Execution of the test code

This step involves invoking the code that is involved with interacting with the mocked object.

An example not specific to any Mock Object Framework:

   PositionProcessor processor = new PositionProcessor();
   processor.process(mockPositionProvider);

In this example we have a PositionProcessor class which processes PositionProviders. A case of code that is involved with interacting with the mocked object is when a PositionProvider is processed we might expect the getX() method to be called at least once.

Verify the expectations were satisfied

This step involves ensuring that everything that was expected of the mock object was satisfied such as the number of method calls to a certain method.

An example in jMock:

   Mockery context = new Mockery();
   <Create mock object>
   <Define the method stubs>
   <Define the expectations>
   <Execute the test code>
   context.assertIsSatisfied();

This example shows a context that is used while using the JMock framework which is initialized at the top. At the end a method assertIsSatisfied() is called on the context. In this case, JMock uses the context to create mock objects and it is aware of what expectations mock objects had. The assertIsSatisfied() method will throw an exception depending on if the expectations of the mock object(s) were or were not satisfied.

Comparison of Mock Object Frameworks (Java)

Mock Object Frameworks provide a language for creation, use, and verification of mock objects. There are a few factors that go into selecting a Mock Object Frameworks, two of the most important are readability and simplicity. Readability is concerned with how natural a Mock Object Framework looks when it is in use. When reading mock object related code it should be very obvious what is going on. Simplicity is concerned with the complexity of using the Mock Object Framework. The less setup and the less a developer has to know to start creating and using mock objects, the better.

jMock, EasyMock, and Mockito are three Java Mock Object Frameworks that have been used in the industry with each being released in that order as can be seen by the improvement over time.

jMock

jMock required a context to be created and using that context, a scope would be defined using an anonymous object where expectations for mock objects would be defined. This is not too unacceptable but it seems awkward, tedious, and in general just something that a developer using this Mock Object Framework should not have to do. Developers would often forget the syntax and have to resort to the jMock cookbook to remember how to do setup the proper structure. An example this is the following:

   public void testSomeAction() {
       Mockery context = new Mockery()
   
       context.checking(new Expectations() {{
           // Setup expectations
       }});
   
       // Test code that interacts with mock objects
   
       context.assertIsSatisfied();
   }

In addition, expectations would have to be specified in such a way that identified everything that would be done with the mock object. In certain cases interactions would have to be ignored because while they were happening, they were not integral what was being tested. A common problem to run into during runtime is a test failing because a method is called when it was not expected to be called.

The expectations that are defined using jMock are quite readable but method chaining would make this easier to type out with the help of an IDE since the following is really two statements:

   oneOf (clock).time(); will(returnValue(fetchTime));

In jMock 1, refactoring was not easy since the method calls were represented as String literals.

EasyMock

EasyMock was an improvement to jMock in the regard of not having to define a scope using an anonymous object as well as being refactorable similar to the newer version of jMock, jMock 2. There was still the idea of a context as there was in jMock however which was unfortunate, this was called a control. The control could have two states, record and replay. In the record state, the control would record expectations for mock objects. In the replay state, the control would record actual calls to the stubbed methods for the mock objects by some means. Switching to the replay state was done using a method called replay() which was called by the control object. At a glance, this looks awkward once again, a developer might not immediately realize this type of state changing behavior upon seeing the following:

   Control control = createStrictControl();  
 
   List one = control.createMock(List.class);                            
   List two = control.createMock(List.class);                            
 
   expect(one.add("one")).andReturn(true);
   expect(two.add("two")).andReturn(true);
 
   control.replay();

   // Test code that interacts with mock objects
   control.verify();

EasyMock still suffers from the same problem dealing with unexpected method calls that jMock does, the developer still needs to ignore those which will be called but they did not declare to expect. There is an improvement over jMock by using method chaining to define stubbed methods for the mock objects, they are defined using a single statement. The syntax of EasyMock is arguably more readable due to this.

Mockito

Mockito was an improvement to EasyMock. Mockito only recognizes two phases of client setup using mock objects which are stubbing and verification. Mockito does not have expectations setup before the method calls are made. Method calls to mocked objects are stored internally in Mockito for when verification takes place. Mock objects are created, stubbed, and then verified. Mockito is very simple having no context or control object being managed in order to mock objects. An example of this is the following:

   LinkedList mockedList = mock(LinkedList.class)
   when(mockedList.get(0)).thenReturn("first")
   // Test code that interacts with mock objects
   verify(mockedList.get(0))

Mockito remedies dealing methods that were not expected to be called by removing the entire expect phase of mocking as a whole. Instead, a developer now verifies that method calls were made after the interacting test code is run. If a method was called but not verified to be called, there is no problem. The readability of Mockito is great, similar to EasyMock when stubbing method calls. The use of static imports can also be seen being used by Mockito which allows method calls to static methods to be called without referencing the class the static method is defined in. In the example above this is used in the mock(), when(), verify() methods. EasyMock also used static imports but Mockito makes more extensive use of it due to not having to use a local context or control object.

Existing Mock Object Frameworks

References

1. Approaches to Mocking

2. Using Mocks And Tests To Design Role-Based Objects

3. Java Mock Frameworks Comparison

4. Wikipedia - Mock object

5. Mockito vs EasyMock