How to unit test code which receives response from server - ios

I have a code fragment which I want to unit test , this code depends upon callback from network stack on event of receipt of data. The network calls are made through a library its basically amazon iOT library, thus I am not directly interacting with iOS network framework but this library. I want to unit test this code, not sure if its possible if yes how.
Attached is the code in question
static func subscribeForData(completionCallBack:((String,NSDictionary)->())?,errorCallBack:((NSError)->())?) {
let iotDataManager = AWSIoTDataManager.default()
let defaults = UserDefaults.standard
let login = .....
iotDataManager.subscribe(toTopic: "testNode/device/"+login, qoS: .messageDeliveryAttemptedAtLeastOnce, messageCallback: {
(payload) ->Void in
let stringValue = NSString(data: payload, encoding: String.Encoding.utf8.rawValue)!
})
}

The best way you can achieve this is through Dependency Injection (DI).
DI can be used as a mean to inject both the real networking code and the "mocked" one.
In order to deal with DI you need to modify your code. In particular an instance of AWSIoTDataManager should be passed to subscribeForData method instead of hardcoding it (why do you have a static method?).
There are different approaches in order to deal with this. One is described for example in The complete guide to Network Unit Testing in Swift. I think that if you read it, you will acquire something new useful for the feature.
Your "mocked" class (I put with "" since it could be defined as a stub or a spy) would have the same API provided by the real one.
As I stated previously, Unit Tests should be fast and should NOT depend on databases, real networks requests and so on.

Related

How to keep a clean production version of your iOS App?

I develop an iOS App called Swordy Quest:
https://apps.apple.com/us/app/swordy-quest-an-rpg-adventure/id1446641513
It contains Game Center integration for Leaderboards, Achievements, Player vs Player (PVP) matchmaking and Clans.
I have a local test version that I use when developing (with a test bundleID). I also have a production version of my game that I use to play the game and progress as if I was a customer. However, in order to upgrade/implement the Game Center functionality above, I need to use my production bundleID for testing. This then overwrites my 'customer game' with all my test data (ruining my 'natural' progress).
So I am wondering, is it possible to have a 'clean' production version of an app and still have a separate test version that allows me to test Game Center functionality. Or is there some way to restore a previous app state in Xcode so I could save my production clean version before polluting it with test data? I know in Mac Apps you can change the custom working directory, but I don't think you can in iOS?
I have looked into backing up my Production version of the app before working on Game Center upgrades, but it looks like this is probably not possible? Has anyone come up with a clever way around this?
Please note I have stored both CoreData and UserDefaults in the app.
Custom working directory is something only command-line tool projects. ChangeCurrentDirectoryPath option is no longer available at this place as the screenshot below in XCode 4.6.1. Sounds crazy but you can try downgrade to Xcode 4 and make it happen.
Or you will need load files using Cocoa’s NSBundle class or Core Foundation’s CFBundle functions. So make duplicate target for your Swordy Quest test. It will not affect your clean copy.
Manage schemes:
Finally click the little gear button create a clean copy to avoid touch your production code.
After you set up your keys both product and test where
Build Settings > Packaging ( write to filter Packaging )
Implement as a code below to your logic function ( for example implement in it to a function which trigger a GameHomeVC from LoginPlayerVC )
var key: String?
#if TARGET_PROD || TARGET_STORE
key = #"prodKey";
#else
key = #"testKey";
as a precursor, i'm not familiar with Game Center, so there may be concerns there that i haven't accounted for. so, with that, my instinct in solving this starts out with launch arguments. there is a great article on how to do this here: https://www.swiftbysundell.com/articles/launch-arguments-in-swift/.
Now that you're able to start changing behavior based off of launch arguments from different schemes, you can start to look at how to segment your test / prod data.
As I'm not a CoreData expert, i can't say with 100% confidence that this is possible (or easy), but i would investigate how to setup separate persistent stores based off of a launch argument. using this article as a reference, it seems like you could roughly do something like the below after creating a -testGameCenter launch argument to a new TestGameCenter scheme to create an in-memory data store when testing Game Center
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "YourDataStore")
if CommandLine.arguments.contains("-testGameCenter") {
let description = NSPersistentStoreDescription()
description.url = URL(fileURLWithPath: "/dev/null")
container.persistentStoreDescriptions = [description]
}
container.loadPersistentStores(completionHandler: { _, error in
if let error = error as NSError? {
fatalError("Failed to load stores: \(error), \(error.userInfo)")
}
})
return container
}()
if you're able to solve the CoreData problem above, it's time to start looking at how to segment your UserDefaults data. this gross but easy solution that immediately comes to mind is prefixing your UserDefault keys with test when running from your test scheme. below is an example of how could structure a wrapper around UserDefaults to manage this
struct UserDefaultsWrapper {
let userDefaults: UserDefaults
let keyPrefix: String
init(userDefaults: UserDefaults, keyPrefix: String) {
self.userDefaults = userDefaults
self.keyPrefix = keyPrefix
}
func setValue(_ value: Any?, forKey key: String) {
self.userDefaults.setValue(value, forKey: prefixedKey(forKey: key))
}
func value(forKey key: String) -> Any? {
self.userDefaults.value(forKey: prefixedKey(forKey: key))
}
func prefixedKey(forKey key: String) -> String {
return "\(keyPrefix)\(key)}"
}
}
where you could make use of the wrapper like so
let userDefaultsPrefix = CommandLine.arguments.contains("-testGameCenter") ? "testGameCenter_" : ""
let userDefaultsWrapper = UserDefaultsWrapper(userDefaults: .standard, keyPrefix: userDefaultsPrefix)
to get something more elegant, you could look a little more into UserDefaults to see if you could apply a solution similar to the one for CoreData where there are two entirely separate stores. from a quick glance at this initializer, maybe you could do something as simple as this with your wrapper instead
struct UserDefaultsWrapper {
let userDefaults: UserDefaults
init(userDefaults: UserDefaults) {
self.userDefaults = userDefaults
}
func setValue(_ value: Any?, forKey key: String) {
self.userDefaults.setValue(value, forKey: key)
}
func value(forKey key: String) -> Any? {
self.userDefaults.value(forKey: key)
}
}
where you construct it like so
let userDefaultsSuiteName: String? = CommandLine.arguments.contains("-testGameCenter") ? myTestingGameCenterSuiteName : nil
let userDefaults = UserDefaults(suiteName: userDefaultsSuiteName)
let userDefaultsWrapper = UserDefaultsWrapper(userDefaults: userDefaults)
lastly, from a comment you made on another reply, it sounds like you are also concerned with fresh install scenarios. that said, the approaches i've outlined will not help (at least i don't think) with persisting data across deletes/installs. but, what i think you should think about is if it's necessary to test those delete/install concerns from your production bundle id. could you instead either manually test those concerns from your test bundle id and/or write unit tests around the components that involve those concerns? when you are approaching your testing strategy, it's important to make sure that you're testing the right things at the right layers; testing the wrong things at the wrong layers makes each testing layer much, much harder to execute
Targets is designed to do just that. You set pre-processor macros values to get the compiler to compile specific code based on target / macros values.
In your case, you change path to the customer game / test data file based on selected the target / macro combination.
You can also set a different bundleID for each target.
Once this is all setup you simply just switch between target and compile. The whole thing should just work seamlessly.
Make a backup of your project and then follow this tutorial which covers exactly how to do this:
https://www.appcoda.com/using-xcode-targets/
If the link above is broken in future, just search "Xcode target tutorials"

Test Driven Upload method?

I'm in a situation where I'm trying to use Test Driven Development.
I've got no experienced in Swift, Xcode, Apple, IOS, TDD or even the macbook I'm using for development. Basically I'm a .Net Developer in a very unfamiliar situation.
My current problem arrises from my ignorance on how to make a unit test that test a void method.
I'm trying to make a method that sends a Image to a server.
But my issue here is that I do not know how to test a method that doesn't return a value.
I imagine that my method is going to be something similar to this:
public func Upload(_ image: UIImage)
and I imagine that I'll need to implement some version of URLSession that eventually will have to call a resume() method. but how do I test if this method is doing what it's supposed to be doing without invoking the network ? and after that how do I make an integration test where I can see that the expected result is in fact a file uploaded to the server ?
Currently the server will be on the computer I'm developing on but the actual software will run from a testIphone that I've been issued.
I've been searching online for days now and the best I've come across have been this link http://swiftdeveloperblog.com/image-upload-with-progress-bar-example-in-swift/
But it only approaches bits and pieces I imagine will be part of the solution not the testing of said solution.
I think it's important to add I'm very much against creating to much complexity for testing purposes. testing should be simple and straight forward.
The approach to take is to use a a test double to check that the correct networking calls are made by your upload method. You'll be making an asynchronous call to a networking library, which may be URLSession or may be another library such as AlamoFire. It shouldn't matter to your upload method which library is in use.
To achieve this, you want to avoid directly using URLSession, and use a wrapper which conforms to an interface that you can then mock in your tests. This means that your code will use a different implementation of the networking class at runtime than at test time, and you'll "inject" the correct one as required.
For example, you could have this interface to your networking library:
protocol NetworkRequesting {
func post(data: Data, url: URL)
}
With the following real implementation to be used at runtime:
struct NetworkRequester: NetworkRequesting {
func post(data: Data, url: URL) {
let session = URLSession()
let task = session.uploadTask(with: URLRequest(url: url), from: data)
task.resume()
}
}
However, at test time, you use the following mock instead:
class MockNetworkRequester: NetworkRequesting {
var didCallPost = false
var spyPostData: Data? = nil
var spyPostUrl: URL? = nil
func post(data: Data, url: URL) {
didCallPost = true
spyPostData = data
spyPostUrl = url
}
}
And then, given the following class under test:
class ImageUploader {
let networkRequester: NetworkRequesting
init(networkRequester: NetworkRequesting) {
self.networkRequester = networkRequester
}
func upload(image: UIImage, url: URL) {
}
}
You can test the implementation of upload like so:
class UploadImageTests: XCTestCase {
func test_uploadCallsPost() {
let mockNetworkRequester = MockNetworkRequester()
let uploader = ImageUploader(networkRequester: mockNetworkRequester)
uploader.upload(image: UIImage(), url: URL(string:"http://example.com")!)
XCTAssert(mockNetworkRequester.didCallPost)
}
}
Currently, that test will fail as upload does nothing, but if you put the following into the class under test, the test will pass:
func upload(image: UIImage, url: URL) {
guard let otherUrl = URL(string:"https://example.org") else { return }
networkRequester.post(data: Data(), url: otherUrl)
}
And that's your first TDD cycle. Clearly it's not yet behaving as you'd like, so you need to write another test to make sure that the url used is the one you expect, or the data passed is what you expect.
There are a number of ways to get your code to use the real network requester at runtime, you could have the init method use default parameter values to get it to use NetworkRequester, or use a static factory method to create it, and there are other options like Inversion of Control, which is well beyond the scope of this answer.
The important thing to remember is that you're testing that you make the correct calls to the networking framework, you're not testing the networking framework. I like to keep my protocol interfaces pretty declarative, passing the things required to make a request in any framework, but you might find you prefer to go closer to the metal and essentially mirror the implementation of URLSession - it's up to you, and more of an art than a science, in my opinion.

What is the format of the optionsFile for the IotFDevice class in Quarks?

The IotfDevice Class in Quarks has a constructor with an optionsFile. What is the format of this file and what are the necessary values?
The options file has five values: org, type, id, auth-method, auth-token. This is an example of mine with the values obfuscated.
[device]
org = 4pj4r4
type = rustQuark
id = rustIIa
auth-method = token
auth-token = 8jLWEY(P4SVJl5oi!V
When you register a device the set of values are issued, details and examples can be found here.
A little context.
Internet of Things Foundation (IoTf) is an infrastructure for application's to communicate with devices. IoTf requires that you register the application and all
devices that send data to the application.
Why would you use IoTf? Getting the communication setup between devices is
a pain: firewalls, retry, failover, QoS, debugging, monitoring. This is facility especially nice if you're doing a proof of concept.
For a better description of IoTf look here.

For plug in running on iOS

What I want to implement is as follow:
A-app (calling app) : request the return value of a-string sent as parameter : request(a-string) -> b-string.
B-app (plug-in installed separately by me or others, it plays the role of dictionary or database ) : search a-string from database and return the result (b-string).
With successful experiences of plug-in on android and with Apple's confident rhetoric of plug-in, I thought plug-in, of course, run on iOS. After a lot of hard work, however, I finally found out:
* Note : The creation and use of loadable bundles is not supported in iOS.*
Nonetheless, not giving up, I finally made it with custom URl and pasteboard:
A-app : write a-string and false state to pasteboard & call B-app via custom URL.
B-app : viewDidLoad runs following func and thereafter exit program ; func { read pasteboard and search from database & write the result(b-string) and true state to pasteboard }
A-app : while-loop detects whether state is false or true. if true, catch b-string from pasteboard.
Anyway it works but it's too long thus almost useless. Do you have any idea for better solutions? Why doesn't Apple allow plug-in for iOS? Any responses are welcome. Thank you.
I can't answer why Apple doesn't allow plug-ins, but I can offer some advice on what you're trying to achieve.
The common pattern for sending data back to your application is to implement a callback url, so the A-app would also implement a custom URI and add that to the uri sent to B-app.
B-app would then process the uri as you have already implemented, but then instead of exiting, it simply sends the data you requested in the uri passed to it.
See http://x-callback-url.com for more details and example implementations.

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:
Create a webserver that serves static results to the client (for example here).
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:
wronguser#blahblah.com getting login denied and
rightuser#blahblah.com 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?
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.
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.
You can make a mock web service quite effectively with a NSURLProtocol subclass:
Header:
#interface MyMockWebServiceURLProtocol : NSURLProtocol
#end
Implementation:
#implementation MyMockWebServiceURLProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
return [[[request URL] scheme] isEqualToString:#"mymock"];
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [[a URL] isEqual:[b URL]];
}
- (void)startLoading
{
NSURLRequest *request = [self request];
id <NSURLProtocolClient> client = [self client];
NSURL *url = request.URL;
NSString *host = url.host;
NSString *path = url.path;
NSString *mockResultPath = nil;
/* set mockResultPath here … */
NSString *fileURL = [[NSBundle mainBundle] URLForResource:mockResultPath withExtension:nil];
[client URLProtocol:self
wasRedirectedToRequest:[NSURLRequest requestWithURL:fileURL]
redirectResponse:[[NSURLResponse alloc] initWithURL:url
MIMEType:#"application/json"
expectedContentLength:0
textEncodingName:nil]];
[client URLProtocolDidFinishLoading:self];
}
- (void)stopLoading
{
}
#end
The interesting routine is -startLoading, in which you should process the request and locate the static file corresponding to the response in the app bundle before redirecting the client to that file URL.
You install the protocol with
[NSURLProtocol registerClass:[MyMockWebServiceURLProtocol class]];
And reference it with URLs like
mymock://mockhost/mockpath?mockquery
This is considerably simpler than implementing a real webservice either on a remote machine or locally within the app; the tradeoff is that simulating HTTP response headers is much more difficult.
OHTTPStubs is a pretty great framework for doing what you want that's gained a lot of traction. From their github readme:
OHTTPStubs is a library designed to stub your network requests very easily. It can help you:
Test your apps with fake network data (stubbed from file) and simulate slow networks, to check your application behavior in bad network conditions
Write Unit Tests that use fake network data from your fixtures.
It works with NSURLConnection, new iOS7/OSX.9's NSURLSession, AFNetworking (both 1.x and 2.x), or any networking framework that use Cocoa's URL Loading System.
OHHTTPStubs headers are fully documented using Appledoc-like / Headerdoc-like comments in the header files. You can also read the online documentation here.
Here's an example:
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
return [request.URL.host isEqualToString:#"mywebservice.com"];
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
// Stub it with our "wsresponse.json" stub file
NSString* fixture = OHPathForFileInBundle(#"wsresponse.json",nil);
return [OHHTTPStubsResponse responseWithFileAtPath:fixture
statusCode:200 headers:#{#"Content-Type":#"text/json"}];
}];
You can find additional usage examples on the wiki page.
As far as option 1, I have done this in the past using CocoaHTTPServer and embedding the server directly in an OCUnit test:
https://github.com/robbiehanson/CocoaHTTPServer
I put up the code for using this in a unit test here:
https://github.com/quellish/UnitTestHTTPServer
After all, HTTP is by design just request/response.
Mocking a web service, wether by creating a mock HTTP server or creating a mock web service in code, is going to be about the same amount of work. If you have X code paths to test, you have at least X code paths to handle in your mock.
For option 2, to mock the web service you would not be communicating with the web service, you would be instead be using the mock object which has known responses.
[MyCoolWebService performLogin:username withPassword:password]
would become, in your test
[MyMockWebService performLogin:username withPassword:password]
The key point being that MyCoolWebService and MyMockWebService implement the same contract (in objective-c, this would be a Protocol). OCMock has plenty of documentation to get you started.
For an integration test though, you should be testing against the real web service, such as a QA/staging environment. What you are actually describing sounds more like functional testing than integration testing.

Resources