I want to test this method that doesn't return a value but I want to check if works fine.
Can you give me some suggestions?
func login() {
if Utility.feature.isAvailable(myFeat) {
if self.helper.ifAlreadyRed() {
self.showWebViewController()
} else {
let firstVC = FirstViewController()
self.setRootController(firstVC)
}
} else {
let secondVC = SecondViewController()
self.setRootController(secondVC)
}
}
so what's the best approach to apply unit test here?
Testing side effects is one approach. But for an example like the code in question, I actually prefer a subclass-and-expect approach.
Your code has three different paths.
If feature is available and already red, show web view controller.
If feature is available and not already red, show first view controller.
If feature is not available, show second view controller.
So assuming this login() function is part of FooViewController, one possibility is writing tests that follow this format:
func testLoginFeatureAvailableAndNotAlreadyRed() {
class TestVC: FooViewController {
let setRootExpectation: XCTExpectation
init(expectation: XCTExpectation) {
setRootExpectation = expectation
super.init()
}
override func setRootController(vc: UIViewController) {
defer { setRootExpectation.fulfill() }
XCTAssertTrue(vc is FirstViewController)
// TODO: Any other assertions on vc as appropriate
// Note the lack of calling super here.
// Calling super would inaccurately conflate our code coverage reports
// We're not actually asserting anything within the
// super implementation works as intended in this test
}
override func showWebViewController() {
XCTFail("Followed wrong path.")
}
}
let expectation = expectationWithDescription("Login present VC")
let testVC = TestVC(expectation: expectation)
testVC.loadView()
testVC.viewDidLoad()
// TODO: Set the state of testVC to whatever it should be
// to expect the path we set our mock class to expect
testVC.login()
waitForExpectationsWithTimeout(0, handler: nil)
}
Related
I have a class, MyContainer, which has another custom class as a variable. This other class, MyInterface, is a view controller super class, which gets extended by two other child custom classes, MyVCA and MyVCB. The reason for this is because I have a bottom button which is used across both screens - only the content has to get updated every time, which I do programmatically. There is also a content manager which I use to know which of the two child classes to use, called MyContentManager.
The problem I am having is when I am going from a previous view controller to either MyVCA or MyVCB, because depending on which one it is, a certain task needs to be done or not. I am instantiating the view for MyVCB from the storyboard like so:
let vc = UIStoryboard(name: "Containers",
bundle: Bundle.main).instantiateViewController(withIdentifier:
"my_container") as! MyContainer
vc.contentManager = MyContentManager(type: .type_my_vc_a)
vc.shouldDoTask = true
self.navigationController?.pushViewController(vc, animated: true)
As can be seen I have created a flag, shouldDoTask, that needs to be set at this point (inside a previous view controller). But because it is set to the container super class, the children can not access it. So what needs to happen basically is that this flag needs to get propagated through the path MyContainer -> MyInterface -> MyVCA / MyVCB.
I have tried to use a property for the flag, in MyInterface:
private var _shouldDoTask: Bool = false
var shouldDoTask: Bool {
set { _shouldDoTask = newValue }
get { return _shouldDoTask }
}
And in MyContainer:
var content: MyInterface!
var shouldDoTask: Bool {
set {
if content != nil {
content.shouldDoTask = newValue
}
}
get {
return (content != nil)
? content.shouldDoTask
: false
}
}
Then in MyVCA / MyVCB I can access it like this:
class MyVCA: MyInterface {
func someMethod() {
if self.shouldDoTask {
// do task
}
}
}
This would work nicely, if it wasn't for the fact that the content is still nil when the flag gets set in the previous view controller. This is understandable because of course MyInterface has not been created yet. I am looking for a way past this. I have been thinking about a method that could get called in MyInterface's viewDidLoad method to set the flag, but I can't seem to figure it out.
Any ideas would be appreciated.
Something like this. Check if it helps.
protocol MyInterFaceDelegate {
func setValues()
}
MyInterFace {
let delegate : MyInterFaceDelegate
viewDidLoad() {
delegate.etValues()
}
}
extension MyContainer : MyInterFaceDelegate {
func setValues() {
content.shouldDoTast = self.shouldDoTast
}
}
When you create MyInterFace() after that you set the delegate
content = MyInterFace()
content.delegate = self
Say I have the following:
class ContentSelectableViewController<T: NSManagedObject> : UIViewController { //... }
class PersonSelectionViewController: ContentSelectableViewController<Person> { // ... }
class PlaceSelectionViewController: ContentSelectableViewController<Place> { // ... }
Then in an instance of one of these subclasses, I have some code:
if let navCtrl = self.navigationController {
for viewController in navCtrl.viewControllers.reversed() {
if viewController is ContentSelectableViewController {
log.info("Worked for \(viewController.description)")
}
if let vc = viewController as? ContentSelectableViewController {
// This should be equivalent to the above.
}
}
}
My question is, when I have a stack full of subclasses of this generic baseclass, it doesn't always return true (go into the if statement) when checking if they are of type ContentSelectableViewController and I don't understand why. They inherit from the same baseclass.
EDIT:
I'm guessing it's because of the generic nature of the class. The if statements evaluate to true for the subclass that calls it.
So, it does in fact have something to do with trying to type check a generic class. It would work for the one and not the other because the one making the call implicitly adds its type.
i.e. (Pseudo-Swift)
if viewController is ContentSelectableViewController<Person> { //... }
What I did instead was to define a protocol that ultimately makes these ContentSelectableViewController<T> selectable:
enum ContentSelectionRole: Int {
case none = 0 // no selection going on right now.
case root // i.e. the one wanting content
case branch // an intermediary. think of a folder when looking for a file
case leaf // like a file
}
enum ContentSelectability: Int {
case noSelections = 0
case oneSelection = 1
case multipleSelections = 2
}
protocol ContentSelection {
var selectedObjects: [NSManagedObject] { get set }
var selectionRole: ContentSelectionRole { get set }
var selectionStyle: ContentSelectability { get set }
func popToSelectionRootViewController() -> Bool
func willNavigateBack(from viewController: UIViewController)
}
Making the definition:
class ContentSelectableViewController<T: NSManagedObject> : UIViewController, ContentSelection { //... }
And then, refactored the original post, to get:
#discardableResult func popToSelectionRootViewController() -> Bool {
if let navCtrl = self.navigationController {
for viewController in navCtrl.viewControllers.reversed() {
if let vc = viewController as? ContentSelection {
if vc.selectionRole == .root {
vc.willNavigateBack(from: self)
navCtrl.popToViewController(viewController, animated: true)
return true
}
}
}
}
return false
}
I still don't quite understand the aspect of the language that makes it fail, but this solution works.
Protocol-based Programming seems to be more Swifty anyway...
I have simple ListView that needs to display the records from the network layer. (first screen of the application)
I need to get some opinion as to which will be correct flow so that I can make the unit test cases easily. (No VIPER architecture)
NetworkMgr makes the network calls and create Model objects.
These Model objects needs to be populated in ListTableView.
I have a completion handler method to call the network request which give the model objects.
func getData() {
dataMgr.requestData(url: "String") { (EmployeesArray, error) in
// print(error)
}
}
Now the Question is - For unit testing when I am calling the ListDataTest since the ListVC is in storyboard when it loads the View the viewdidLoad method calls the which will initiate the network logic.
SO I am not able to test only the UI related stuffs.
I tried to create some extension in ListDataTest class but no success is achieved.
Below is the flow of the Controllers : -
===
class ListVC: UIViewController {
var dataProvider: ListData
override func viewDidLoad() {
super.viewDidLoad()
dataProvider.viewLoaded()
}
}
=======
In ListData class
protocol DatProviderLoad {
func viewLoaded()
}
class ListData: NSObject {
}
extension ListData : DatProviderLoad {
func viewLoaded() {
print("loaded")
//the network calls goes here
}
}
/—
The test class
class ListDataProviderTest: XCTestCase {
var sut: ListData!
var controller: ListVC!
override func setUp() {
super.setUp()
sut = ListData()
let storyBoard = UIStoryboard(name:"Main", bundle: nil)
controller = storyBoard.instantiateViewController(withIdentifier: "ListVC") as! ListVC
controller.dataProvider = sut //before this called the storyboard already called the viewdidload once
_ = controller.view
}
}
Your feedback will be very helpful.
Any hint or tutorial in right direction will be highly appreciable.
Lets try to do this in MVVM way.
Think ViewController as part of View layer.
To call the network layer and to convert models into view models introduce a ViewManager.
The Viewcontroller will ask ViewManager to provide the data(ViewModel) and passes all actions(like button press) to ViewManager to handle the business logic.
This way it will be easy to write test cases for ViewManager layer(which is supposed to have all the business logic) and your View is not coupled with either the Network layer or data models.
I want to detective networking state, when networking state changed, show a error view in current controller. But there is a problem by using protocol.
Here is the codes:
private func networkingDetection() {
//This is the detective method in appdelegate
try! reachability.startNotifier()
reachability.whenReachable = { [weak self] _ in
DispatchQueue.main.async {
self?.currentViewController().hideNetworkingErrorView()
}
}
reachability.whenUnreachable = { [weak self] _ in
DispatchQueue.main.async {
self?.currentViewController().showNetworkingErrorView()
}
}
}
And here is the protocol
protocol NetworkingErrorProtocol {
// I want to show the default view if there is no networkingErrorView, and
when declare a custom view in controller, show the custom view.
//var networkingErrorView: UIView? { get }
func showNetworkingErrorView()
func hideNetworkingErrorView()
}
extension UIViewController: NetworkingErrorProtocol {
func showNetworkingErrorView() {
}
func hideNetworkingErrorView() {
}
}
Anyone can tell me how to figure it out? It's really makes me crazy. Thanks a lot.
The issue with your setup is that conforming UIViewController to your protocol does not allow you to receive that call in your subclass. If you try to override the protocol function in your subclass you will get a compiler error: Declarations from extensions cannot be overridden yet
First off, a note about NotificationCenter. If you need multiple parts of your app to be notified of the change that would be a good way to go. If you only need to tell one controller, this is a classic usage for a delegate.
Here are two ways to get the desired functionality: using the delegate pattern and without.
Let's say Manager is the class where the monitoring is happening:
Using a delegate pattern
class Manager {
weak var networkDelegate : NetworkStatusListener?
func monitorNetworkStatus() {
var reachable = true;
if reachable {
// We can call the delegate directly
networkDelegate?.networkStatusChanged(.connected)
}
else {
networkDelegate?.networkStatusChanged(.disconnected)
}
}
}
And the same Manager without a delegate pattern. This would be the simplest fix for your current implementation issue.
class Manager {
func currentViewController() -> UIViewController { return vc }
func monitorNetworkStatus() {
var maybeAtListener = currentViewController()
// DON't SHIP THIS, but it can be helpful during development to make sure you didn't forget to conform one of your classes
assert(maybeAtListener is NetworkStatusListener, "Oops, did you mean to conform \(currentVC) to NetworkStatusListener")
var reachable = true;
if reachable {
// We can't be sure the controller conforms to the protocol but we can try
(maybeAtListener as? NetworkStatusListener)?.networkStatusChanged(.connected)
}
else {
(maybeAtListener as? NetworkStatusListener)?.networkStatusChanged(.connected)
}
}
}
Then for your view controller
class MyController : UIViewController, NetworkStatusDelegate {
func networkStatusChanged(_ status: NetworkStatus) {
switch status {
case .connected:
// Normal UI
break
case .disconnected:
// No network connect
break;
}
}
}
Also, not directly related to your question but for this example I used a slightly different approach to the protocol design that can be helpful for "status" oriented protocols. Having multiple functions can often become a little more tedious to conform to as protocols get larger.
enum NetworkStatus {
case connected
case disconnected
}
protocol NetworkStatusListener : class {
func networkStatusChanged(_ status: NetworkStatus)
}
Try using reachability class's NSNotificationCenter
add this in appdelegate's didFinishLaunchingWithOptions if you want for whole app
OR add in your specific viewcontroller if you want this in specific Viewcontroller
NotificationCenter.default.addObserver(self, selector:Selector(("checkForReachability:")), name: NSNotification.Name.reachabilityChanged, object: nil);
let reachability: Reachability = Reachability.forInternetConnection();
reachability.startNotifier();
This method called while network state changed .
func checkForReachability(notification:NSNotification)
{
let networkReachability = notification.object as! Reachability;
_ = networkReachability.currentReachabilityStatus()
// do yor additional work here
}
I have two functions that are nearly identical and I want to merge them into one but I cannot find out how to handle the type casting in my if-let statement. There are only two solutions that I can think of but I cannot execute either of them.
Here are the two functions (there's a lot more to them but this is the only part that is causing me trouble in the merge):
func loadNextEventViewController() {
if let nextEventViewController = storyboard?.instantiateViewController(withIdentifier: "EventViewController") as? EventViewController {
// Executed code in here
}
}
func loadFinishViewController() {
if let finishViewController = storyboard?.instantiateViewController(withIdentifier: "FinishViewController") as? FinishViewController {
// Executed code in here
}
}
My first attempt was to make a generic parameter that could accept either the EventViewController OR FinalViewController, but as far as I can tell, there is no logical OR for generic parameters, only logical AND.
My second attempt was to create a computer variable but this didn't work either.
How can I take an argument in my function call that I could cast to be either class type in my if-let block?
Example:
func loadViewController(identifier: String, viewControllerType: UIViewController)
I've solved this issue in a very clunky way by using an in-else statement but I'd like to find a more elegant way of solving this problem.
You can do it this way
func load(_ viewController: UIViewController) {
if viewController is EventViewController {
//do stuff
}
if viewController is FinishViewController {
//do stuff
}
//do stuff that applies to all types of view controllers
}
Then call it like this:
let eventVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("EventViewController")
load(eventVC)
If you want to avoid using if statements you can use a switch statement instead, like so:
func load(_ viewController: UIViewController) {
switch viewController {
case is EventViewController:
//do stuff for EventViewController
case is FinishViewController:
//do stuff for FinishViewControllers
default:
//do stuff for other types, or break
}
//do stuff that applies to all viewControllers
}
This is not a problem of casting.. in your application you have two options, move to one vc or the other. There could be alot of logic that makes this decision or not. but either way, a decision HAS to be made at some point on what VC to show. You just need to decide on the best place to make this decision.
based on what you've shown so far, you could just do:
func showController(identifier: String) {
if let vc = storyboard?.instantiateViewController(withIdentifier: identifier) as? UIViewController {
// Executed code in here
}
}
I presume though, that each route has a different actions that require handling differently. The most common approach here is to use a segue, then you can use prepareForSegue to capture the segue and set properties as required based on the segue identifier. If you can provide some more information I can provide a better answer.
The main thing you need to consider here are.. whats actually different between the two. determine this and you can refactor your code to reduce repetition
You can do it like this
func loadViewController(identifier: String) {
let vc = storyboard?.instantiateViewController(withIdentifier: identifier) as? ViewController
if let eventVC = vc as? EventViewController {
// Executed code in here
} else if let finishVC = vc as? FinishViewController {
// Executed code in here
}
}