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

How to use SMS to attract and recall lost users?

There are three common methods of user recall or ...

The photos of "red fire ants" you see are probably wrong...

A bite from a red fire ant can cause severe pain ...

Where can I drink tea in Ningbo? I recommend this one absolutely

Ningbo tea tasting and SPA recommendations, high-...

Is there a new form of magnetism? Scientists have discovered cross magnetism!

Li Zhaoying Magnetism is a concept that we are ve...

How to create a CocoaPods in Swift

You may be familiar with some well-known open sou...

The fifth episode of the Aite tribe clinic: data collection and front-end application

【51CTO.com original article】 Activity description...

Traffic analysis in fission growth mode

Fission growth has always played an important rol...

Android 9.0 will ban developers from using unofficial APIs

According to the developer forum XDA, a recent su...