iOS unit testing: translation - common ways to use OCMock

iOS unit testing: translation - common ways to use OCMock

The API used in this article is the old version of OCMock API. The new version is also compatible with the old version of the API. The translator has added the corresponding new version (OCMock3) API for readers' reference where the old version of the API is used.

Enthusiasts

This article assumes that readers are familiar with using Xcode5's test framework XCTest, or the BBD test tool Kiwi or other iOS test frameworks.

[[133631]]

What is a mock? It’s basically a paper tiger.

When writing unit tests, it is inevitable to instantiate as few concrete components as possible to keep the tests short and fast. And to keep the units isolated. In modern object-oriented systems, the component under test is likely to have several dependent objects. Instead of instantiating the concrete dependent classes, we use mocks. A mock is a fake stand-in object with predefined behavior for the concrete object in the test. The component under test doesn't know the difference! Your component is designed within a larger system, and you can test your component with mocks with confidence.

Common mock use cases

Stub Method

We start by explaining the general stub syntax in OCMock with a simple example.

  1. id jalopy = [OCMock mockForClass[Car class ]];
  2. [[[jalopy stub] andReturn:@ "75kph" ] goFaster:[OCMArg any] units:@ "kph" ];
  3.   // if returning a scalar value, andReturnValue: can be used  

OCMock3 new version corresponding API

  1. id jalopy = OCMStrictClassMock([Car class ]);
  2. OCMStub([jalopy goFaster:[OCMArg any] units:@ "kph" ]).andReturn(@ "75kph" );
  3. // if returning a scalar value, andReturnValue: can be used  

This simple example first mocks a jalopy from the Car class, then stubs the goFaster: method to return the string @"75kph". The stub syntax may look a little strange, but it is a common practice:

  1. ourMockObject stub] whatItShouldReturn ] method:

OCMock3 new version corresponding API

  1. OCMStub([ourMockObject method:]).andReturn()

A very important note: Note the use of [OCMArg any]. When specifying a method with parameters, if the method is called and the parameters are the specified parameters, the mock will return the value specified by andReturn:. The [OCMArg any] method tells the stub to match all parameter values. For example:

  1. [car goFaster:84 units:@ "mph" ];

The stub will not be triggered because the last parameter does not match "kph".

Class Methods

OCMock will look for a class method with the same name if no instance method with the same name is found on the mock instance. In the case of the same name (class method and instance method have the same name), use classMethod to specify the class method:

  1. [[[[jalopy stub] classMethod] andReturn:@ "expired" ] checkWarrany];

In OCMock3, the stub methods of classMethod and instanceMethod are the same, for example:

  1. id classMock = OCMClassMock([SomeClass class ]);
  2. OCMStub([classMock aClassMethod]).andReturn(@ "Test string" );
  3. // result is @"Test string"  
  4. NSString *result = [SomeClass aClassMethod];

mock type – niceMock, partialMock
OCMock provides several different types of mocks, each with their own specific use cases.

Use this method to create any mock:

  1. id mockThing = [OCMock mockForClass[Thing class ]];

OCMock3 new version corresponding API

  1. id mockThing = OCMStrictClassMock([Thing class ]);

This is what I call a 'vanilla' mock. A 'vanilla' mock will throw an exception when a method is called that is not stubbed. This results in a monotonous mock, where every method call must be stubbed during the mock's lifecycle. (See the next section on stubs for more information)

If you don't want to stub a lot of methods, use a 'nice' mock. A 'nice' mock is very polite and won't throw an exception when a non-stubbed method is called.

  1. id niceMockThing = [OCMock niceMockForClass[Thing class ]];

OCMock3 new version corresponding API

  1. id mockThing = OCMClassMock([Thing class ]);

The last type of mock is a 'partial' mock. When a method that is not stubbed out is called, the method will be forwarded to the real object. This is a technical cheat of the mock, but it is very useful when there are classes that do not lend themselves well to stubbing.

  1. Thing *someThing = [Thing alloc] init];
  2. id aMock = [OCMockObject partialMockForObject:someThing]

OCMock3 new version corresponding API

  1. Thing *someThing = [Thing alloc] init];
  2. id aMock = OCMPartialMock(someThing);

Verify that the method was called

Verifying that a method was called is very simple. This can be accomplished with expect to reject and verify methods:

  1. id niceMockThing = [OCMock niceMockForClass[Thing class ]];
  2. [[niceMockThing expect] greeting:@ "hello" ];
  3.   // verify the method was called as expected  
  4. [niceMocking verify];

OCMock3 new version corresponding API

  1. id niceMockThing = OCMClassMock([Thing class ]);
  2. OCMVerify([niceMockThing greeting:@ "hello" ]);

An exception is thrown when the method being verified is not called. If you are using XCTest, wrap the verification call with XCTAssertNotThrow. The same is true for rejecting method calls, but an exception is thrown when the method is called. Just like the stub, the selector and the parameters passed to the verification must match the parameters passed when the call is made. Using [OCMArg any] can simplify our work.

Processing block parameters

OCMock can also handle block callback parameters. Block callbacks are commonly used in network code, database code, or in any asynchronous operation. In this example, consider the following method:

  1. - ( void )downloadWeatherDataForZip:(NSString *)zip
  2. callback:( void (^)(NSDictionary *response))callback;

In this example, we have a method that downloads zipped weather data, and delegates the downloaded dictionary to a block callback. In the test, we test the callback handling with predefined weather data. It's also a smart way to test failure scenarios. You never know what the network will return to you!

  1. // 1. stub using OCMock andDo: operator.  
  2. [[[groupModelMock stub] andDo:^(NSInvocation *invoke) {
  3.          //2. declare a block with same signature  
  4.          void (^weatherStubResponse)(NSDictionary *dict);
  5.          //3. link argument 3 with with our block callback  
  6. [invoke getArgument:&weatherStubResponse atIndex:3];
  7.          //4. invoke block with pre-defined input  
  8. NSDictionary *testResponse = @{@ "high" : 43 , @ "low" : 12};
  9. weatherStubResponse(groupMemberMock);
  10. }]downloadWeatherDataForZip@ "80304" callback:[OCMArg any] ];

OCMock3 new version corresponding API

  1. // 1. stub using OCMock andDo: operator.  
  2. OCMStub([groupModelMock downloadWeatherDataForZip:@ "80304" callback:[OCMArg any]]]).andDo(^(NSInvocation *invocation){
  3.          //2. declare a block with same signature  
  4.          void (^weatherStubResponse)(NSDictionary *dict);
  5.          //3. link argument 3 with with our block callback  
  6. [invoke getArgument:&weatherStubResponse atIndex:3];
  7.          //4. invoke block with pre-defined input  
  8. NSDictionary *testResponse = @{@ "high" : 43 , @ "low" : 12};
  9. weatherStubResponse(groupMemberMock);
  10. });

The general idea here is fairly simple, but its implementation requires some explanation:

1. This mock object uses the "andDo" method with an NSInvocation parameter. An NSInvocation object represents an 'objectivetified' (I really don't know what this is) representation of a method call. Through this NSinvocation object, it becomes possible to intercept the block parameter passed to our method.

2. Declare a block parameter with the same method signature as in the method we are testing.

3. The NSInvocation instance method "getArgument:atIndex:" passes the assigned block function to the block function defined in the original function. Note: In Objective-C, the first two parameters passed to any method are "self" and "_cmd". This is a small feature of the runtime and something we need to consider when using subscripts to get NSInvocation parameters.

4. Finally, pass this callback a predefined dictionary.

at last

Hopefully this article and examples have made clear some of the most common uses of OCMock. The OCMock site: http://ocmock.org/features/ is a great place to learn about OCMock. Mocking is tedious but necessary for a modern OO system. If a dependency graph is difficult to test with mocks, it's a sign that your design needs to be rethought.

<<:  Win10: The Last Windows

>>:  An agenda sheet reveals the secrets of Google I/O

Recommend

Understand the growth gossip model of user operations in one article!

The Growth Gossip Model can be described in one s...

Microsoft, you should be held accountable for the "pay whore" incident

The Alipay staff certainly didn't expect that...

Tumor markers elevated? Don’t panic! Here’s the truth!

Tumor markers elevated? Don’t panic! Here’s the t...

How much money did you lose from that 9.9 yuan product?

Double Eleven, Double Twelve, Double Holidays... ...

Tencent Advertising's guide to attracting customers!

1. Industry Background Preview 1. Development tre...

String theory in the garden

How to quantize gravity is a puzzle for theoretic...