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.
Related
I'm a beginner in Unit Test and I would like to test my cases in a switch but I don't know how to do it.
I have :
- (void)testClickSmiley
{
[self.viewController click:nil];
// Here What i do ? I use what kind of XCTest Assertions ? I want to test if it goes into "default" for example
}
And in my ViewController :
- (IBAction)click:(id)sender
{
UIButton *btn = (UIButton *)sender;
switch (btn.tag) {
case Bad:
// Show view Bad
break;
case Average:
// Show view Average
break;
case Good:
// Show view Bad
break;
default:
break;
}
}
Of course, I don't want to modify my ViewController.
Any ideas ? TY
What you actually should be doing in this case is writing UI tests for this scenario. Your context and execution environment do not allow you to test your code based on unit tests (for example, the app is not aware of any button you pass to the test) the way you expect it.
Of course the first thing that is wrong is that you use
[self.viewController click:nil];
The click function will get a nil value for the button and the tag will therefore be nil as well.
Of course you could mock a button:
UIButton *button = [[UIButton alloc] initWith...]
button.tag = [YourEnum].Bad
[self.viewController click: button];
But that would still leave you with the problem that you don't know where the switch ended up going...
Solution (if applicable):
Take a look at UI Testing
https://developer.apple.com/videos/play/wwdc2015/406/
It allows you to run the application and simulate user interactions + you have the benefit that you can always assume you are working with the actual button that caused the click: event in the first place.
There is nothing wrong with exercising your view controller in a straight unit test without using UI testing. In fact, I would have more unit tests and fewer UI tests (if any).
Why? It depends on the purpose of your tests. The reason I test is to get fast feedback to enable refactoring and TDD. I need tests that are fast and dependable, not slow and fragile.
So go ahead and write tests that invoke your view controller. To your question, "What do I do here?" You verify the actions that would be taken. For example, you can test
Changes to the view
Changes to the underlying model
Calling the next view controller with the expected data
It's unusual to have a single IBAction method act as a fan-out of multiple actions. This ties them together unnecessarily. A change to a single action could break other actions. Instead, consider creating multiple IBAction methods, one per action.
To see an example of how to write unit tests for a UIViewController — in fact, how to TDD it — see my screencast How to Do UIViewController TDD.
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/
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.
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.
}
I have simple class for perform network stuff. It's a singleton and it encapsulates NSOperationQueue inside it. When class' user calls some method to getting data from network, this class creates proper instance of operation class inherited from NSOperation sets up it and adds to queue for performing. Obviously, that performing is making asynchronously in separated threads. After getting data from network NSOperation inherited object notifies my network class and it notifies interested delegates about data getting finished or error.
Question is, how can I make unit tests for checking network class' logic? Also, I don't actually want to test server side behavior. I just want to replace actual async call to server with mock and predefined answers to after test handlers' behavior. I want to check how are my classes work, not server side. I understand commonly logic for testing stuff like that but I little bit confused with using OCMock for it.
Best answer will be code example. I'm using OCUnit and OCMock in my project for unit testing.
Also any articles or github links will be perfect.
If all the asynchronous calls go through an internal method in your class, you can simply create a partial mock on your object and use stub/expect on that method. You can then call the public methods as normal and use the mock to verify that the internal method is called. Using the partial mock stops the real implementation from being called, so no network activity should occur.
As to the other half, the call-backs from the asynchronous operation, simply call the method that would be called directly from your tests, then check that your class does the right thing, either by checking its state with OCUnit asserts, or, if it in turn uses callbacks, with another mock.
So I know this is regarding OCMock... but I thought I'd put it out there that I do this successfully with Kiwi and it looks like this.
it(#"should refresh the client's temporary API key if it is stale before sending the request", ^{
ISLDataServiceAdd *addRequest = [ISLDataServiceAdd withRecord:#{ISLFieldContact_FirstName: #"Jason"} table:ISLTableContact];
[[clientMock shouldEventually] receive:#selector(apiKey) andReturn:VALID_API_KEY];
[[clientMock shouldEventually] receive:#selector(hasTemporaryAPIKey) andReturn:theValue(YES)];
[[clientMock shouldEventually] receive:#selector(isTemporaryAPIKeyStale) andReturn:theValue(YES)];
[[clientMock shouldEventually] receive:#selector(refreshTemporaryAPIKeyAndWait:)];
[addRequest sendRequestUsingClient:clientMock completion:nil failure:nil];
});
sendRequestUsingClient:completion:failure: is an asynchronous call, so by using shouldEventually with Kiwi, it knows that it needs to wait some time (default is 1 second) before those selectors will be called.