Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stubbing/mocking up webservices for an iOS app

I'm working on an iOS app whose primary purpose is communication with a set of remote webservices. For integration testing, I'd like to be able to run my app against some sort of fake webservices that have a predictable result.

So far I've seen two suggestions:

  1. Create a webserver that serves static results to the client (for example here).
  2. Implement different webservice communication code, that based on a compile time flag would call either webservices or code that would load responses from a local file (example and another one).

I'm curious what the community thinks about each of this approaches and whether there are any tools out there to support this workflow.

Update: Let me provide a specific example then. I have a login form that takes a username and password. I would like to check two conditions:

  1. [email protected] getting login denied and
  2. [email protected] logging in successfully.

So I need some code to check the username parameter and throw an appropriate response at me. Hopefully that's all the logic that I need in the "fake webservice". How do I manage this cleanly?

like image 734
EightyEight Avatar asked May 29 '12 22:05

EightyEight


People also ask

What is stubbing and mocking?

Stubbing, like mocking, means creating a stand-in, but a stub only mocks the behavior, but not the entire object. This is used when your implementation only interacts with a certain behavior of the object.

What are stubs in Swift?

Stubbing in Swift Stubs are simple and straightforward — they're essentially the simplest possible implementation of a method and return the same canned data every time.

What is mock in unit testing?

What is mocking? Mocking is a process used in unit testing when the unit being tested has external dependencies. The purpose of mocking is to isolate and focus on the code being tested and not on the behavior or state of external dependencies.


2 Answers

I'd suggest to use Nocilla. Nocilla is a library for stubbing HTTP requests with a simple DSL.

Let's say that you want to return a 404 from google.com. All you have to do is:

stubRequest(@"GET", "http://www.google.com").andReturn(404); // Yes, it's ObjC 

After that, any HTTP to google.com will return a 404.

A more complete example, where you want to match a POST with a certain body and headers and return a canned response:

stubRequest(@"POST", @"https://api.example.com/dogs.json"). withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}). withBody(@"{\"name\":\"foo\"}"). andReturn(201). withHeaders(@{@"Content-Type": @"application/json"}). withBody(@"{\"ok\":true}"); 

You can match any request and fake any response. Check the README for more details.

The benefits of using Nocilla over other solutions are:

  • It's fast. No HTTP servers to run. Your tests will run really fast.
  • No crazy dependencies to manage. On top of that, you can use CocoaPods.
  • It's well tested.
  • Great DSL that will make your code really easy to understand and maintain.

The main limitation is that it only works with HTTP frameworks built on top of NSURLConnection, like AFNetworking, MKNetworkKit or plain NSURLConnection.

Hope this helps. If you need anything else, I'm here to help.

like image 131
luisobo Avatar answered Oct 07 '22 17:10

luisobo


I am assuming you are using Objective-C. For Objective-C OCMock is widely used for mocking/unit testing (your second option).

I used OCMock for the last time more than a year ago, but as far as I remember it is a fully-fledged mocking framework and can do all the things that are described below.

One important thing about mocks is that you can use as much or as little of the actual functionality of your objects. You can create an 'empty' mock (which will have all the methods is your object, but will do nothing) and override just the methods you need in your test. This is usually done when testing other objects that rely on the mock.

Or you can create a mock that will act as your real object behaves, and stub out some methods that you do not want to test at that level (e.g. - methods that actually access the database, require network connection, etc.). This is usually done when you are testing the mocked object itself.

It is important to understand that you do not create mocks once and for all. Every test can create mocks for the same objects anew based on what is being tested.

Another important thing about mocks is that you can 'record' scenarious (sequences of calls) and your 'expectations' about them (which methods behind the scenes should be called, with which parameters, and in which order), then 'replay' the scenario - the test will fail if the expectations were not met. This is the main difference between classical and mockist TDD. It has its pros and cons (see Martin Fowler's article).

Let's now consider your specific example (I'll be using pseudo-syntax that looks more like C++ or Java rather than Objective C):

Let's say you have an object of class LoginForm that represents the login information entered. It has (among others) methods setName(String),setPassword(String), bool authenticateUser(), and Authenticator* getAuthenticator().

You also have an object of class Authenticator which has (among others) methods bool isRegistered(String user), bool authenticate(String user, String password), and bool isAuthenticated(String user).

Here's how you can test some simple scenarios:

Create MockLoginForm mock with all methods empty except for the four mentioned above. The first three methods will be using actual LoginForm implementation; getAuthenticator() will be stubbed out to return MockAuthenticator.

Create MockAuthenticator mock that will use some fake database (such as an internal data structure or a file) to implement its three methods. The database will contain only one tuple: ('rightuser','rightpassword').

TestUserNotRegistered

Replay scenario:

MockLoginForm.setName('wronuser'); MockLoginForm.setPassword('foo'); MockLoginForm.authenticate(); 

Expectations:

getAuthenticator() is called MockAuthenticator.isRegistered('wrognuser') is called and returns 'false' 

TestWrongPassword

Replay scenario:

MockLoginForm.setName('rightuser'); MockLoginForm.setPassword('foo'); MockLoginForm.authenticate(); 

Expectations:

getAuthenticator() is called MockAuthenticator.isRegistered('rightuser') is called and returns 'true' MockAuthenticator.authenticate('rightuser','foo') is called and returns 'false' 

TestLoginOk

Replay scenario:

MockLoginForm.setName('rightuser'); MockLoginForm.setPassword('rightpassword'); MockLoginForm.authenticate(); result = MockAuthenticator.isAuthenticated('rightuser') 

Expectations:

getAuthenticator() is called MockAuthenticator.isRegistered('rightuser') is called and returns 'true' MockAuthenticator.authenticate('rightuser','rightpassword') is called and returns 'true' result is 'true' 

I hope this helps.

like image 39
malenkiy_scot Avatar answered Oct 07 '22 15:10

malenkiy_scot