How do you test the SwiftUI views? - ios

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!

Related

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

Trouble when using Unit testing

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.

Mock initial viewController - code coverage results

I've a problem and I don't know the solution even after several hours of try and error and googling and stackoverflowing.
I have a view controller. I would like to pass per dependency injection an object. This object derives from a protocol. In general it is not a problem to setup a unit test. Also mocking works and the unit tests are running. So where's the problem?
I am testing only one class in my primary target. This class has absolutely nothing to do with view controller. But the code coverage is showing me a decent percent value of covering the view controllers. After a while I found out that when I hit the "test" button the project gets executed as if I push the "run" button. And because of that the view controller gets initialized and created and I have no chance to pass another dependency first, or before the tests getting executed.
So I need a method to distinguish between a test run and a real run, to pass in one case a real object and in the other case the fake object.
And my question is, how to to that? I wonder why nobody have this problem. I mean what gives me the code coverage tool if it shows me that methods are covered even though I haven't tested them.
The one and only class that I am testing:
And these are the coverage results (The bars are just gray because Xcode lost focus during screenshot. Otherwise they are blue.):
So I was expecting to see covered in the results just the class I am testing and not everything else. I know why this problem persists. The view controller has a dependency and this dependency after it gets initialized creates some more classes and so on. What I would like to do is to pass a fake object during unit testing and a real object during a real run. Just like It works in Visual Studio for non ui tests: If the tests are executed the application does NOT start up. The test runner just initialize the subjects under test and that's all. And this is what I want to achieve for iOS unit tests. I guess I've missed sth. very important :(
For all of us who have or will have the same problem. The solution is to specify environment variables for the test run. After that you can check if you are running your unit tests with code like this (assuming you have created an environment variable called "InTestMode" and set the value to "1" during test run:
let dict = NSProcessInfo.processInfo().environment
if let env = dict["InTestMode"] as? String?
{
return env == "1"
}
return false

How to test UI Interaction and manipulation of UINavigationController

I'm new to BDD and I'm trying to figure out how I could write a test for pressing the back button in my app, or whether BDD requires me to write a test at all.
Here's a few example scenarios with functionality:
What: tapBackButton
Scenario: formNotSaved
Result: showAlertNotifiyingTheUser
What: tapBackButton
Scenario: formIsSaved
Result: goes1ScreenBack
I have no idea how to write unit tests for this! I have added the OCMock framework but it doesn't seem like you're allowed to change a uiviewcontroller's navigation controller since its read-only.
I really want to change my development process to write a failing test first and then code, but this makes it difficult.
Thanks for you time!
I'm not aware of Apple development, but I can comment on standard TDD stuff and that should get you in the right direction - I hope :)
What I'd do for this is have your "Alert" code in a seperate class with an interface, say IAlerter (unless Apple framework already has an interface for "messageboxes/alerts" that you can leverage).
Inject the interface into the constructor of your UI class.. Then in the test, you mock out the IAlerter in the test.
so basically.. (in C#, Moq (mocking library) style, Pseudocode sorry ;))
//ARRANGE
var alerterMock = new Moq.Mock<IAlerter>();//fake the alerter - cuz you really don't want it to happen, you just want to do a "Verify" that it "was" called.
var ui = new UI(alerterMock.Object);
//ACT
ui.StartDataEntry();
ui.GoBack();
//ASSERT
alerterMock.Verify(a => a.ShowAlert(Moq.It.IsAny<string>()));//verify ShowAlert was called with any string - or at least do a lil ".Contains" to make sure it was calling code that shows the text you expect.
HTH :)

Resources