Swift Unit Testing IBAction - ios

I have a UIViewController that contains each of UITextField, UIButton and UILabel. I put something in the UITextField, press a button and the string is now capitalized in the UILabel.
My question is: how do I set up the IBAction in Swift for unit tests? If there is no way to test the action in Swift, what else can I do to test this?

In the unit-testing world, the most difficult work could be UI testing. So, what you can do is to check whatever is available to you from the API.
You can not toggle an action or event like you are a phone user. So, you have to programmatically toggle actions or events in order to test on that. You will also have to programmatically initialize the UI elements yourself.
IBAction is just nothing but an indicator to tell UIStoryboard that this is a connector method, you can ignore and treat it as a normal method.

Unit Tests are not designed to test visual interface. It's made to test your code.
For testing the interface, you can use Apple's UI Automation tool or other external tools.

Personally I tried to do a mock for the class and send the following code:
controllerMock.btnNoResults.sendActionsForControlEvents(UIControlEvents.TouchUpInside)
The problem is that the call is not being made to the mock so the test is useless.
The same method executed in Objective-C with OCMock works perfectly so what I do is I mix swift tests and Objective-C tests.
In order to setup the Objective-C test to test swift source code I need to basically do the following:
Add OCMock to my test project
Create an Objective-C test. It will ask me for the bridging header as usual
Import "OCMock.h"
VERY IMPORTANT: Import the header to access the swift functions on my project. This header is normally "ProjectName_Tests-Swift.h"
Use the following code:
-(void) testSeeMoreInformationAction{
id mock = [OCMockObject partialMockForObject:pController];
[[mock expect]seeMoreInformation];
[pController.btnSeeMoreDetails sendActionsForControlEvents:UIControlEventTouchUpInside];
[mock verify];
}
Like this my test works. Also the good thing of mixing Objective-C with Swift and adding OCMock is that you now can test many other things in an easier way. Please note that when you use OCMock and Swift it only works with iOS SDK functions, it will not work well with you swift functions.
Here is an example of another OCMock test very useful with AlertView:
- (void)testLoginShowsAlertViewWhenNoUserNameAndPassword{
pController.txtUserName.text = #"";
pController.txtPassword.text = #"";
id mock = [OCMockObject partialMockForObject:pController];
[[mock expect]presentViewController:OCMOCK_ANY animated:YES completion:nil];
[pController loginAction];
[mock verify];
}
Please feel free to contact me if you need further assistance with Unit Testing. I think is one of the most interesting parts of swift development.

Related

OCMock and UIViewController

I am currently researching on how to efficiently add some unit tests to my app's ViewControllers. So far it worked pretty well until I tried to that that a specific view controller presents another one.
I am using OCMock and XCTest. The test is as follows
id partialMock = OCMPartialMock([TestViewController class]);
[partialMock doSomeStuff];
OCMVerify([partialMock presentViewController:[OCMArg any] animated:[OCMArg any] completion:[OCMArg any]]);
As you can see, I only want to verify that presentViewController was called to the tested view controller inside doSomeStuff function. Please note that the given example is a simplified version of what I currently have. Main difference being that I am verifying that the argument viewController is another mocked object.
Problem is since doSomeStuff method is not stubbed, the call is then forwarded to the real TestViewController instance, which then calls presentViewController on itself, then not firing the partialMock's verification.
Is there something I am missing? Or is it truly undoable what I am trying to achieve?
You can stub the method you want to supress by using andDo(nil) as described in 2.10: http://ocmock.org/reference/

Some Test Questions About iOS XCTest

how to test viewController, viewdidload & viewdidAppear, in test case it will never invoke. how to test my custom viewdidload code?
Are all test cases concurrent? or which test case will be invoke first in all test file. I want to fetch some data from network before all test case, how to implement it?
For 1. You can create UIAutomation bot for testing your code. If you don't want any integration tests, you can try calling directly following functions like [viewController viewDidLoad]
For 2. tests in one file are executed alphabetically, I saw a lot of people to do test like 001_testSomething, 002_testSomethingElse etc.
If you want to prepare your data you can do it in setUp function:
- (void)setUp
{
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}

Testing if performSegueWithIdentifier is called within a view controllers method

I am going through an application and adding Unit Tests. The application is written using storyboards and supports iOS 6.1 and above.
I have been able to test all the usual return methods with no problem. However I am currently stumped with a certain test I want to perform:
Essentially I have a method, lets call it doLogin:
- (IBAction)doLogin:(UIButton *)sender {
// Some logic here
if ( //certain criteria to meet) {
variable = x; // important variable set here
[self performSegueWithIdentifier:#"memorableWord" sender:sender];
} else {
// handler error here
}
So I want to test that either the segue is called and that the variable is set, or that the MemorableWord view controller is loaded and the variables in there are correct. The variable set here in the doLogin method is passed through to the memorableWord segues' destination view controller in the prepareForSegue method.
I have OCMock set up and working, and I am also using XCTest as my unit testing framework. Has anyone been able to product a unit test to cover such a situation??
It seems that Google and SO are pretty bare in regards to information around this area.. lots of examples on simple basic tests that are pretty irrelevant to the more complex reality of iOS testing.
You're on the right track, your test wants to check that:
When the login button is tapped doLogin is called with the loginButton as the sender
If some criteria is YES, call performSegue
So you should actually trigger the full flow from login button down to performSegue:
- (void)testLogin {
LoginViewController *loginViewController = ...;
id loginMock = [OCMockObject partialMockForObject:loginViewController];
//here the expect call has the advantage of swallowing performSegueWithIdentifier, you can use forwardToRealObject to get it to go all the way through if necessary
[[loginMock expect] performSegueWithIdentifier:#"memorableWord" sender:loginViewController.loginButton];
//you also expect this action to be called
[[loginMock expect] doLogin:loginViewController.loginButton];
//mocking out the criteria to get through the if statement can happen on the partial mock as well
BOOL doSegue = YES;
[[[loginMock expect] andReturnValue:OCMOCK_VALUE(doSegue)] criteria];
[loginViewController.loginButton sendActionsForControlEvents:UIControlEventTouchUpInside];
[loginMock verify]; [loginMock stopMocking];
}
You'll need to implement a property for "criteria" so that there is a getter you can mock using 'expect'.
Its important to realize that 'expect' will only mock out 1 call to the getter, subsequent calls will fail with "Unexpected method invoked...". You can use 'stub' to mock it out for all calls but this means it will always return the same value.
IMHO this seems to be a testing scenario which has not properly been setup.
With unit tests you should only test units (e.g. single methods) of your application. Those units should be independent from all other parts of your application. This will guarantee you that a single function is properly tested without any side effects.
BTW: OCMock is great tool to "mock out" all parts you do not want to test and therefore create side effects.
In general your test seems to be more like an integration test
IT is the phase of software testing, in which individual software modules are combined and tested as a group.
So what would I do in your case:
I would either define an integration test, where I would properly test all parts of my view and therefore indirectly test my view controllers. Have a look at a good testing framework for this kind of scenario - KIF
Or I would perform single unit tests on the methods 'doLogin' as well as the method for calculating the criteria within your if statement. All dependencies should be mocked out which means within your doLogin test, you should even mock the criteria method...
So the only way I can see for me to unit test this is using partial mocks:
- (void)testExample
{
id loginMock = [OCMockObject partialMockForObject:self.controller];
[[loginMock expect] performSegueWithIdentifier:#"memorableWord" sender:[OCMArg any]];
[loginMock performSelectorOnMainThread:#selector(loginButton:) withObject:self.controller.loginButton waitUntilDone:YES];
[loginMock verify];
}
Of course this is only an example of the test and isn't actually the test I am performing, but hopefully demonstrates the way in which I am having to test this method in my view controller. As you can see, if the performSegueWithIdentifier is not called, the verify with cause the test to fail.
Give OCMock a read, I have just bought a book from amazon about Unit Testing iOS and its really good to read. Looking to get a TDD book too.

SenTestingKit setUp and tearDown overrides get called twice

I am using KIF to test our iOS app. I am trying to make some tests that will go before and after my whole test sweet. I made a SenTestSuite category and overrode -setUp and -tearDown:
-(void)setUp
{
[tester loginCurrentVersion];
NSLog(#"setup");
}
-(void)tearDown
{
[tester logoutFromAnywhereIfNeeded];
NSLog(#"teardown");
}
These methods do get called, but my problem is that they both get called twice. I can't access any of the SenTestSuite.m methods. I am unsure why they are getting called twice. Why is it doing this and how can I solve this?
Thanks!!
Using a category to override a class's methods is really, really iffy. Instead, subclass SenTestCase and put your -setUp and -tearDown there. Then have your test classes inherit from it.
Since you are using KIF, your setUp and tearDown methods should be the beforeAll and afterAll. I also suggest you to take a look at the sample application and try to understand those tests.

cannot stub class method with OCMock 2.1+ in Xcode 5.0

I know that OCMock version 2.1+ supports stubbing class methods out of the box. But for some reason it's not working with me. To make sure I isolated the problem, I simply cloned the example OCMock project (which is clearly marked as version 2.2.1) and simply added this inside testMasterViewControllerDeletesItemsFromTableView:
id detailViewMock = [OCMockObject mockForClass:[DetailViewController class]];
[[[detailViewMock stub] andReturn:#"hello"] helloWorld];
in DetailViewController.h I added:
+ (NSString *)helloWorld;
and DetailViewController.m:
+ (NSString *)helloWorld {
return #"hello world";
}
But I keep on getting the error:
*** -[NSProxy doesNotRecognize Selector:helloWorld] called!
to see a demo of the problem please clone this repo to see what's going on.
That should work just fine. I just tried in a project of mine which uses XCTest on Xcode5, and that code passed.
I would 1) make sure you are using the latest version of OCMock (which is 2.2.1 right now; I think there are some fixes for both class methods and Xcode5 in the newer versions), and 2) make sure your DetailViewController class is linked in the runtime (i.e. part of the correct target) correctly.
In looking at your project, your DetailViewController class is part of both the main application, and the test target. With Xcode5, it appears this means that two copies of the class get compiled and are present in the runtime, with code in the app calling one copy, and code in the test case calling the other. This used to be a linker error (duplicate symbols), but for better or worse, the linker now appears to silently allow two copies of the same class (with the same name) to exist in the ObjC runtime. OCMock, using dynamic lookup, finds the first one (the one compiled into the app), but the test case is directly linked to the second copy (the one compiled into the test bundle). So... OCMock is not actually mocking the class you think it is.
You can see this, just for grins, by verifying as part of the test case that [DetailViewController class] will not equal NSClassFromString(#"DetailViewController") (the first is directly linked, the second is dynamic).
To fix this properly, in the "Target Memberships" for DetailViewController.m, just uncheck the test target. This way there is only one copy of the class in the runtime, and things work like you'd expect. The test bundle gets loaded into the main application, so all of the main application's classes should be available to the bundle without having to directly compile them into the bundle. Classes should only be part of one of the two targets, not both (this has always been the case).
Could you show the code you are testing?
This works:
#interface DetailViewController : UIViewController
+ (NSString *) helloWorld;
#end
#implementation DetailViewController
+ (NSString *)helloWorld
{
return #"hello world";
}
#end
The test:
- (void) test__stubbing_a_class_method
{
id mockDetailViewController = [OCMockObject mockForClass:[DetailViewController class]];
[[[mockDetailViewController stub] andReturn:#"hello"] helloWorld];
STAssertEqualObjects([DetailViewController helloWorld], #"hello", nil);
}
Looking at your sample project:
You should not be compiling DetailViewController.m in your test target.
You should not have any references to OCMock in your primary target.
I removed all reference to OCMock from both projects, then just included OCMock from source and the test passes just fine. I think you probably just have some environmental conflicts that are causing your problem.
Although Carl Lindberg's answer is the correct one, I figured i'd summarize what we discussed in his answer's comments here:
The problem was simply that I was using an out dated version of
OCMock. The reason I got there was b/c the instructions on the
ocmock page simply referred me to grab the
iOS example off their github account and copy over the OCMock
library (They even instructed to use the same directory structure).
It turns out that the library in their example is over 2 years
old!!.
To remedy that, simply run the build.rb script on shell like so:
ruby build.rb. This will give you an up to date libOCMock.a library, which you can simply plug back in to your project, and Voila! it's all done!
just use
id detailViewMock = [OCMockObject niceMockForClass:[DetailViewController class]];

Resources