iOS Unit Test on switch case - ios

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.

Related

Unit testing methods which update UI elements in iOS

I am trying to write unit tests for my existing code.
I have 3 methods.
func methodOne() {
// code
methodTwo()
}
func methodTwo() {
// code
methodThree()
}
func methodThree() {
// code
// update UI element
}
What's the ideal way to unit test such methods. While unit testing methodTwo() it calls methodThree() as the UI elements are not loaded they have nil value when methodThree() is called. How to unit test methods which involve UI elements. I don't want to test if the UI element is loaded properly, I just want to test the code in methodTwo() and methodThree(). Is there a way to bypass UI elements related code. Any help is appreciated. Thank you.
The best way to do this would be to separate the code you want to test from your UI as much as you can. This could be done by putting the //code part of methodTwo into its own function (ie methodFour) and calling it from methodTwo. Then your code would look like this:
func methodTwo() {
methodFour()
methodThree()
}
func testMethodFour() {
methodFour()
// check assumptions
}
func methodFour() {
// code that you originally had in methodTwo before methodThree
}
Hi #Pradeep 👋, welcome to StackOverflow.
From the way you describe the failures you are experiencing I'm guessing your methods are #IBOutlets in a UIViewController, it that correct?
If that's the case, you can make the tests initialize the UIViewController view by doing something like this before calling the methods that depend on the view being available:
_ = viewControllerUnderTest.view
or
viewControllerUnderTest.beginAppearanceTransition(true, animated: false)
This is a decent post to learn more about the gotchas of testing UIViewControllers, this has a more advanced approach that could help you if you end up writing a lot of tests with dependencies on the view like you described.
I would encourage you, though, to keep as much business logic code as you can aways from UIViewControllers and UIViews. The lifecycle of these classes makes them a bit trickier to test, and the simplest you can make something to test the better.
Write all your business logic in dedicated classes and structs, they will be simpler to test if they don't subclass other types. Use UIViewControllers only as glue code to show data to the user and forward their inputs to your business logic, so that you'll have to write only a few straightforward tests. Keep the UIViews humble, with only logic to configure them.
(If my UIViewController guess was wrong, please add more details and we'll figure out a way to get your tests to not crash.)

In Unit Tests how can I programmatically dismiss system permission dialog?

I have an unit test which calls methods on CNContactStore() e.g. CNContactStore().execute(saveRequest). So the permission dialog for contacts pops up, like the Push notifications alert but the contacts permission dialog doesn't get dismissed automatically. I know how to do this in UI tests with addUIInterruptionMonitor() but have no idea how to do this in unit test.
I would create a wrapper around CNContactStore and then use a mock when testing.
You're not really interested in testing CNContactStore, you are interested in testing that your code interacts with CNContactStore properly right?
Setup
I would start out creating protocols and classes to extract the contact stuff out of your "normal" code base.
First a Contact struct to hold the properties you need later to create an actual CNContact
struct Contact {
//holds whichever properties you need to create a CNContact
}
Then a protocol to hold the methods you would like to execute. This could be done with a protocol with a lot of methods like so
protocol ContactsHolder {
func save(contact: Contact)
func add(contact: Contact)
func delete(contact: Contact)
func update(contact: Contact)
//Maybe more methods, the important thing is that you abstract yourself away from CNContactStore and other Contact kit classes
}
Or you could create an enum holding the possible options like so
enum ContactsUpdateMethod {
case save(Contact)
case add(Contact)
case delete(Contact)
case update(Contact)
}
protocol ContactsHolder {
func execute(_ method: ContactsUpdateMethod)
}
In Your "Real" Code
With that in place, you are ready to create your actual ContactsHolder, which then internally uses CNContactStore and everything related to that framework.
So for instance (if you chose the version with a "pure" save function)
class CNContactsHolder: ContactsHolder {
func save(contact: Contact) {
//1. create a `CNContact` from your `Contact`
//2. create a saveRequest
//3. execute: CNContactStore().execute(saveRequest)
}
....
}
And then you give the class(es) who needs to work with CNContactStore a reference to your new ContactsHolder protocol
So in your class you have
let contactsHolder: ContactsHolder
And then you can either pass it in, in your init method
init(contactsHolder: ContactsHolder = CNContactsHolder()) {
self.contactsHolder = contactsHolder
}
Or you can declare it as a var and then give it a default value
So instead of:
let contactsHolder: ContactsHolder
You say:
var contactsHolder: ContactsHolder = CNContactsHolder()
The important thing is that you can change the ContactsHolder from being a "real" CNContactsHolder into a mock when you need to test
In Your Test Code
To test this, you create a mock:
struct MockContactsHolder: ContactsHolder {
var saveWasCalled = false
func save(contact: Contact) {
saveWasCalled = true
}
}
And then you use that in your class instead of the CNContactsHolder
Now you should be able to test your own code, without getting interrupted with permissions and stuff that is not relevant to your code, but is a consequence of using CNContactStore.
Disclaimer :)
I haven't run the above by a compiler, so there may be typos.
Also, there might be bits and pieces missing to make it fit to CNContact (callbacks and so on), but I hope you get the idea about how to split things apart.
And finally...it may seem like a lot of work, but I think it makes sense to get the "framework specific" code out into a separate helper class, hid behind a protocol, so that you can swap it out whenever you need to do testing for instance, or...if you decide to get rid of CNContact at a later point and use a different frameworks.
Hope it helps.
I think you're confusing Unit Testing with UI Testing. In Unit Testing, you just want to test, your codes (e.g. functions and properties) and with that, you'll most probably need to have "mock-up".
For instance, you want to test your login button selector that has a network calls after validation of the input fields.
The following should be the steps:
Test your validation logic. Both failing and succeeding cases.
Test the code inside the completion block of your API call, BUT not using the REAL API data. Instead, use your mocked API here.
and so on...
Now, back to your question, you don't need to handle that uncontrollable and "un-dismissable" alert controller generated by the system. Instead, what you wanna do is to "mock" (ughh not again) that pop-up event by hitting the delegate function for that access-contacts alert by the system, "mock" a response namely "Don't Allow" and "OK". What do you expect to happen when user taps on the first button? The second button? Set expectations/assert.
That's it. Hit every function you need to hit to increase the coverage of your code. Let me know if this helps.

How to create a test that passes when a function is triggered?

I am trying to unit test a two functions in a view controller class. The two functions will create a user and sign in a user respectively. My tests are not UI related.
As of now, I simply need to create a test that passes when one of the functions is called. The goal is to do this without changing the current view controller implementation if possible and just keep it all in the testing class/function.
How do I go about this?
Thanks
I'm assuming a view controller with a method that invokes either "create user" or "sign in user". (You say it's not UI related, but we can easily test button taps if that's the trigger.)
class MyViewController: UIViewController {
func triggeringMethod() {
// Calls one of the two methods below
}
func createUser() {
// Does stuff
}
func signInUser() {
// Does stuff
}
}
And it sounds like you want to test the flow, but not the effect. That is, you want to test "Was createUser() called?" There are several ways to get what you want, but the better ways will require you to change your view controller implementation.
Making a Partial Mock
A standard trick from Working Effectively With Legacy Code by Michael Feathers is "Subclass and Override Method". Let's start there. In test code, we can make
class TestableMyViewController: MyViewController {
override func createUser() {
}
override func signInUser() {
}
}
So far, this is a way of stubbing out the effects of these methods. But we can now add the mocking techniques from my try! Swift Tokyo talk.
class TestableMyViewController: MyViewController {
var createUserCallCount = 0
var signInUserCallCount = 0
override func createUser() {
createUserCallCount += 1
}
override func signInUser() {
signInUserCallCount += 1
}
}
Now you can call the triggering method, and check the call counts.
(Changes you may have to make: The class can't be final. The methods can't be private.)
Moving the Workers
While this is a fine place to start, don't stop there. What we've created is a "partial mock". That's where we've kept most of the functionality, but mocked out a couple of the methods. This is something to avoid. The reason is that we end up with class that mixes production code and test code. It would be far too easy to end up in a situation where you're inadvertently testing test code instead of testing production code.
What the partial mock makes clear is that we're missing a boundary. The view controller is doing too much. The actual work of "create user" and "sign in user" should be performed by another type (possibly even 2 types). In Swift, we can define this boundary with a protocol. That way production code can use the real functionality, while for test code we can inject mocks.
This means the production code should avoid deciding for itself who does the actual work. Instead, we should tell it who does the work. That way, tests can provide alternative workers. Specifying these dependencies from the outside is called "Dependency Injection".
Passing Back Effects
Another option lets us avoid mocks altogether. Instead of testing whether something was called, we can describe the desired effect in an enumeration. Then we can define effects like
enum Effect {
case createUser(CreateUserRequestModel)
case signInUser(SignInUserRequestModel)
}
Instead of the triggering method calling createUser() or signInUser(), it would call a delegate. (Another option is to pass in closures instead of specifying delegates.)
protocol Delegate {
perform(_ effect: Effect)
}
Then in the triggering method,
delegate?.perform(.createUser(parameters))
This means it's up to the actual delegate to transform these enumeration values into actual work. But it makes the tests easy to write. All we need is to provide a testing implementation that captures the Effect value.

Swift Unit Testing IBAction

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.

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.

Resources