Trouble when using Unit testing - ios

Is anyone know, in unit testing, how can we test buttons, function and tableView in Swift 3.
Example I have a button action:
#IBAction func BellAction(_ sender: Any) {
let searchVC = self.storyboard?.instantiateViewController(withIdentifier:StoryBoardIDs.Notification.rawValue)
self.navigationController?.pushViewController(searchVC!, animated: true)
}
To test this:
let home = HomeDashnboard() // This is my class
home.viewDidLoad() // test viewDidLoad()
home.BellAction() // shows an error

Unfortunately you can't directly call/manipulate your view controllers, views, etc, like that. These classes lifecycle/flow are controlled by UIKit and called, on very specific moments, during your iOS app regular execution. It would be highly error prone trying to emulate such behavior yourself :-(
But, if you want to test your app UI, I highly recommend taking a closer look into the UI Testing framework also provided by Xcode:
UI testing gives you the ability to find and interact with the UI of your app in order to validate the properties and state of the UI elements.
UI testing includes UI recording, which gives you the ability to generate code that exercises your app's UI the same way you do, and which you can expand upon to implement UI tests. This is a great way to quickly get started writing UI tests.

Related

How do you test the SwiftUI views?

I am building a SwiftUI application to manage movies. I am using MVVM approach for SwiftUI and I ended up with the following unit test for my View model to verify that the validation is working and the message is displayed on the screen, when the TextField is empty. I wrote the following test.
func testExample() throws {
let vm = AddMovieViewModel()
vm.title = ""
vm.year = ""
vm.addMovie()
XCTAssertEqual(vm.message, "Error occurred.")
}
But I have a feeling that this test is not doing much and it is testing the implementation details instead of the behavior. If I refactor the addMovie to saveMovie without changing the implementation then the test will fail. Even though the behavior has not changed.
For SwiftUI, how do you test the UI? Few options I have heard are
Manual testing using Xcode Previews
ViewInspector
E2E Testing to test the complete story instead of just the behavior.
Thoughts!

How can i make unit test cases of Login screen in iOS swift

I am having an issue on the logIn screen. how can I make a unit test case with a login screen? I have username and password screen with an action button. I need to call an API when clicking on a button and make some unit test cases. Please provide me some information. thanks in advance.
The simplest approach is to structure your code so that it doesn't actually initiate the login API call. Instead, it:
Creates the request, but stops before sending it
Handles the response
Then you can test that filling in the fields and tapping the button creates the correct request. After that, you can test various responses, including all sort of error cases that are hard to create in end-to-end testing.
To tap a button from a unit test, make it so that the test can access the button. Then call sendActions(for: .touchUpInside)
Example: There are many ways to structure this. Let's say we have a protocol
protocol NetworkCalling {
typealias CallResult = Result<(Data, URLResponse), Error>
typealias CompletionHandler = (CallResult) -> Void
func call(request: URLRequest, completionHandler: #escaping CompletionHandler)
}
Our view controller will use whatever it's given. It doesn't care. It just knows how to make a URLRequest from its properties. It also knows how to handle the result, for both success and failure.
class ViewController: UIViewController {
var networkCall: NetworkCalling?
#IBAction private func login(sender: AnyObject) {
let request = URLRequest(url: URL(string: "http://foo.bar?baz")!)
networkCall?.call(request: request) { [weak self] result in
self?.handleResult(result)
}
}
private func handleResult(_ result: NetworkCalling.CallResult) {
switch result {
case let .success(data, response):
break
case let .failure(error):
break
}
}
}
The protocol introduces a boundary. The view controller can't see past that boundary. It's not the view controller's business. The protocol gives us opportunities to provide different implementers:
Something that makes real network calls.
A Decorator that wraps another implementer, doing logging.
A Test Spy that captures its arguments for unit testing.
A fake that replays stored responses for UI testing. This makes UI tests faster and more reliable.
You can write multiple test cases by sending different parameters for Username and Password.
Wrong usernames and passwords should give you error message and then you can check if you are receiving the error message instead of success message.
Entering right username and password should give you success messages as response.
Use XCTAssertEqual to compare messages.
There could be better UITest cases on this screen.
Although the question seems to be kind of broad, let me mention some of the useful points:
In unit test, we care about the functionality of the scene instead of caring about visual UI components -for example-. So, what are you saying about testing about login:
I need to call an API when clicking on a button and make some unit
test cases.
is valid, it is one of the main functionality in the scene.
You should focus on testing your code, but not the API itself. In other words, your test cases should be implemented in a way that you expect a certain response (such as success or fail), therefore what should happen when receiving it, it means that do not try to test the API response itself (we are not doing integration tests here), you should care about what your code behaves based on the response.
Regarding to the above point, how to apply it? You could follow the approach of "mocking" the response. Briefly, what you could do is to create mock class that contains the same behaviors for the used network manager thus you can hard-code the received results (assumptions) as -for instance- success of failure thus testing what should happen for the received "mocked" result.

Should I write tests first (following TDD) for the view layer or only do only manual testing and add snapshot tests when finished?

As expected, I normally have difficulties when following TDD (i.e. writing the tests first) with the view layer.
Namely, in order to observe or trigger certain visible changes (layout or styling) I would need to make the view's internals public. This breaks encapsulation and allows for client code do some thing like myView.label.text = "User".
To avoid this, I either add getter methods to UIView class:
var userName: String{ return label.text }
Or do some extensions that are added only to the test framework:
extension MyView{
//avoids making a getter just for the sake of testing, while keeping it private and interchangeable
var userName : String{
return (viewWithTag(someLabelTage) as! UILabel).text
}
A different approach is to skip the TDD work flow (i.e. test manually after the feature is done) and add snapshot testing (see https://github.com/pointfreeco/swift-snapshot-testing) to the increase coverage and have a safety net when refactoring.
Considering all this, I was wondering if there are any other patterns or approaches, that I can use to be more efficient while keeping my confidence in the code.
Not an expert in Swift, but regardless of the language/framework, something tells me you're actually making your task harder.
I normally have difficulties when following TDD with the view layer.
This is expected. The View support to be simple and have no behavior (i.e domain logic) at all. It should be simple user interaction, and binding data to the controls in the view. So you don't need TDD or to be more precise Unit Tests around the View in my opinion. You're not going to get much value out of trying to write Unit tests for the View.
You View can be tested more effectively using a UI test framework, such as Selenium, or your own UI test framework, which tests User interactions. This way you more return in investment (ROI) than attempting to TDD the View layer.
I don't have much to add to Spock's answer. Tests should be written to help development and maintenance of the code in the future. If they get in the way then either the code is not architected properly or it's code for which the ROI is low.
A pattern that is worth mentioning is the humble view. There are a few ways to approach it, each with different trade offs, but the gist is to have data structures defining how the view should look like, and then let the view read these data structure and set its properties from them.
This allows you to test that the data structure driving the view is generated properly, without having to test the view itself, because it's nothing but an humble object that reads and sets values.

XCUITest Order of Test Execution

Is there any known way to influence the order in which test methods are called by XCUITest? Currently it seems to be completely arbitrary which test method is called in which order.
This makes it impossible to define a specific order. In general the way how Xcode manages XCUITest runs is sub-optimal in that you have to define a test function for every single test case. It would be much more desirable to launch a complete test session from only one method so one can structure their own tests in for example session : features : scenarios.
... That's exactly what I'm trying to do because we're following a Calabash-style test structure and a framework I've wrote around XCUITest provides a lot of additional functionality (such as Testrail integration).
I've tried to implement the framework in such a way that all features and scenarios are organized and started from a single test() method. This would work if Xcode would allow for it but as it turns out terminating and launching the app between every scenario is problematic as it causes Main thread stalls or even crashes. So I've added support to our framework to do it the old-fashion Xcode way of having to define a method for every test but the order is messed up by Xcode which messes up the generation of logs and reports for Testrail.
XCUITest executes tests in alphabetical ascending order(unless randomised). You can name the test methods like the following to run in the order you want
test01FirstCase() { //test code }
test02ThirdCaseButRunsSecond() { //test code }
test03SecondCaseButRunsThird() { //test code }
If you want your app not to launch and terminate after every test method so simply take XCUIApplication().launch() method out of Setup method and put it into very first test function at top of everything, this is how app will launch only once and second test execution will start exactly from the point where first test is ended.
If you want to maintain execution sequence one way is to define any one of following order
func Test01(){}
func Test01(){}
func Test_A(){}
func Test_B(){}
func Test01_AnyString(){}
func Test02_AnyString(){}
or if you want to start all the execution from single method follow the steps below
func Test01_veryFirstTestMethod(){
secondTestMethod()
thirdTestMethod()
forthTestMethod()
so on............
}
Note: your secondTestMethod, thirdTestMethod, forthTestMethod won't have test keyword in function declaration

Shared tests in XCTest test suites

I have a class which can be configured to do 2 slightly different things. I want to test both paths. The class is a descendant of UIViewController and most of the configuration takes place in Interface Builder. i need to verify that both Storyboard Scenes and their Outlets are wired up in the same way, but also need to check for the difference in behavior.
I'd like to use shared XCTest suites for this purpose.
One is aimed for use with the left hand, one for the right. Both appear after another when using the app. The first one (right hand) triggers a segue to the other. The last one (left hand) should trigger a different segue. This is where it differs, for example.
Now I want to verify the segues with tests. I'd like to create a BothHandSharedTests suite which both view controller instance tests use to verify everything they have in common. However, the BothHandSharedTests class is treated as a self-containing test suite, which it clearly isn't.
I came up with these strategies:
inherit from an abstract XCTest descendant, like described above (doesn't seem to be that easy),
write a test auite for the common properties and use one of the two as the Object Under Test, and add two smaller suites for the differences.
How would you solve this problem?
Here's a solution in Swift:
class AbstractTests: XCTestCase {
// Your tests here
override func perform(_ run: XCTestRun) {
if type(of: self) != AbstractTests.self {
super.perform(run)
}
}
}
I don't have a conclusive answer, but here's what I ended up doing.
I first tried the subclassing route. In the parent test (the "AbstractTestCase") I implemented all the tests that would be executed by the the AbstractTestCase subclasses, but added a macro so they don't get run by the actual parent test:
#define DONT_RUN_TEST_IF_PARENT if ([[self className] isEqualToString:#"AbstractTestCase"]) { return; }
I then added this macro to the start of every test, like so:
- (void)testSomething
{
DONT_RUN_TEST_IF_PARENT
... actual test code ...
}
This way, in the ConcreteTestCase classes which inherit from AbstractTestCase, all those tests would be shared and run automatically. You can override -setUp to perform the necessary class-specific set-up, of course.
However – This turned out to be a crappy solution for a couple reasons:
It confuses Xcodes testing UI. You don't really get to see a live representation of what's running, and tests sometimes don't show up as intended. This makes clicking through to debug test failures difficult or impossible.
It confuses XCTest itself – I found that tests would often get run even when I didn't ask them too (if I were just trying to run a single test) and the so the test output wouldn't be what I would expect.
Honestly it felt a little janky to have that macro – macros which redirect flow control are never really that good of an idea.
Instead, I'm now using a shared object, a TestCaseHelper, which is instantiated for each test class, and has has a protocol/delegate pattern common to all test cases. It's less DRY – most test cases are just duplicates of the others – but at least they are simple. This way, Xcode doesn't get confused, and debugging failures is still possible.
A better solution will likely have to come from Apple, unless you're interested in ditching your entire test suite for something else.

Resources