i am using swift-snapshot-testing library to do snapshot testing. It's important for me to do all the tests with different localization to check all the UIs when the text is changed.
For example, the UI testing scheme is set to run on German language and region, In some tests, I want to see the english version as well and I did that
class BackgroundViewFactoryTests: XCTestCase {
override class func setUp() {
SnapshotTesting.diffTool = "ksdiff"
}
func test_backgroundView_contact_eng() {
continueAfterFailure = false
XCUIApplication().launchArguments += ["-AppleLanguages", "(en)"]
XCUIApplication().launchArguments += ["-AppleLocale", "en_UK"]
XCUIApplication().launch()
let view = BackgroundViewFactory()
view.backgroundColor = .darkGray
assertSnapshot(matching: view, as: .image, record: false)
}
}
But it's still not working and run on German language, could anyone give me hit?
Thanks
The snapshot tests should be agnostic to all Strings. You don’t want breaking tests if you change one translation.
Test the translation in a separate UnitTest.
Related
I have a cell that contains a textView and i would like to test that the properties of that textView are set correctly using unit tests. However i seem to have hit a blocker when it comes to having access to the textView in the test since its private.
Is there a way I can test my textView:-
Here is my code
class MyCell {
private let myText: UITextView = {
let textView = UITextView()
textView.isScrollEnabled = false
textView.isEditable = false
return textView
}()
func setup(viewModel: MYViewModel) {
if viewModel.someValue {
myText.backgroundColor = UIColor.red
} else {
myText.backgroundColor = .clear
}
}
}
Is it possible to test something like the testView's background color is set to clear ?
Unless you want to relax the access level of myText from private to internal (the default if you don't specify it), there is no direct way to test it.
The only suggestion I have to test this indirectly would be to use snapshot testing.
You could write two snapshot tests, one for each value of .someValue from your MYViewModel.
Another option to make the testing –and maintainability– of your view straightforward is to introduce a ViewConfiguration value type, following the humble view pattern.
Basically, you can have a struct in between MYViewModel and MyCell that describes each of the view properties for MyCell.
struct MyCellViewConfiguration {
let textFieldBackgroundColor: UIColor
}
extension MYViewModel {
var viewConfiguration: MyCellViewConfiguration = {
return MyCellViewConfiguration(
textFieldBackgroundColor: someValue ? .red : .clear
)
}
}
extension MyCell {
func setup(with configuration: MyCellViewConfiguration) {
myText.backgroundColor = configuration.textFieldBackgroundColor
}
}
The code in setup(with configuration: MyCellViewConfiguration) is so simple –just a 1-to-1 assignment– that you can get away without testing it.
You can then write test for how MyCellViewConfiguration is computed from MYViewModel.
Simply remove private from your declaration. Then the access control will be the default internal. In the test code, make sure to #testable import so gain access to internal features.
Unit testing a few attributes isn't hard. But if you want a test that records the appearance, look into snapshot testing. This can be done without XCUITestCase. It's an order of magnitude slower than a regular unit test, but probably another order of magnitude faster than a UI test.
I have recently heard that using dependency injection is "the only socially acceptable way to use a singleton in today's software development world". I don't necessarily want to debate the accuracy of this statement right now, as it is mostly opinion-based. My goal right now is to understand how exactly I can use dependency injection with the singleton pattern.
For example, in my latest iOS app, I have a Service layer where I keep my URLSession code. I created this layer as a singleton:
struct ServiceSingleton {
private init()
static let shared = ServiceSingleton()
func fetchJSON() {
// URLSession code
}
}
I then use shared in my ViewController, as below:
class ViewController: UIViewController() {
override viewDidLoad() {
super.viewDidLoad()
fetchData()
}
fileprivate func fetchData() {
ServiceSingleton.shared.fetchJSON()
}
}
Of course, the code above uses a singleton, but it does not use dependency injection. I am aware that if I wanted to use dependency injection in general, I would add something like this to ViewController:
// Dependency Injection Constructor
override init(someProperty: SomePropertyType) {
self.someProperty = someProperty
super.init()
}
TL;DR:
(1) Could you show me how to properly use dependency injection with the singleton pattern in Swift?
(2) Could you explain to me what this achieves?
(3) Should I always use DI when I use the singleton pattern in my iOS projects from now on?
Could you show me how to properly use dependency injection with the singleton pattern in Swift?
Rather than accessing ServiceSingleton.shared directly, you access an instance variable that is injected into your object, usually in the initializer if possible, otherwise as a settable property, post-initialization:
protocol FooService {
func doFooStuff()
}
class ProductionFooService: FooService {
private init() {}
static let shared = ProductionFooService()
func doFooStuff() {
print("real URLSession code goes here")
}
}
struct MockFooService: FooService {
func doFooStuff() {
print("Doing fake foo stuff!")
}
}
class FooUser {
let fooService: FooService
init(fooService: FooService) { // "initializer based" injection
self.fooService = fooService
}
func useFoo() {
fooService.doFooStuff() // Doesn't directly call ProductionFooService.shared.doFooStuff
}
}
let isRunningInAUnitTest = false
let fooUser: FooUser
if !isRunningInAUnitTest {
fooUser = FooUser(fooService: ProductionFooService.shared) // In a release build, this is used.
}
else {
fooUser = FooUser(fooService: MockFooService()) // In a unit test, this is used.
}
fooUser.useFoo()
Typically initialization of ViewControllers is done by your storyboards, so you can't ingect your dependancies via initializer parameters, and will have to instead use stored properties that are set after object initialization.
Could you explain to me what this achieves?
Your code is no longer coupled to ProductionFooService.shared. As a result of this, you can introduce different implementations of FooService, such as one for a beta environment, a mock one for unit testing, etc.
If all your code pervasively directly uses your prod dependancies, you'll...
find that it's impossible to instantiate your objects in a test environment. You don't want your unit tests, CI test environments, beta environments, etc. connecting to prod databases, services and APIs.
Have no true "unit" tests. Every test will be testing a unit of code, plus all of the common dependancies that it transitively depends on. If you were to ever make a code change to one of these dependancies, it would break most of the unit tests in your system, which makes it harder to pin down exactly what failed. By decoupling your dependancies, you can use mock objects that do the bare minimum necessary to support a unit test, and ensure that each test is only testing a particular unit of code, and not the transitive dependancies it relies on.
Should I always use DI when I use the singleton pattern in my iOS projects from now on?
It's a good habit to pick up. Of course, there are qucik-and-dirty-projects for which you just want to move fast and won't really care, but it'll surprise you how many of these supposed qucik-and-dirty-projects actually take off, and pay the cost down the road. You just need to be cognizant of when you're hindering yourself by not taking some extra time to decouple your decencies.
I am using Swinject for Dependency Injection. I have created a DependencyManager which has a shared instance of container.
internal class DependencyManager {
private static let sharedInstance = DependencyManager()
private var assembler: Assembler
private let container: Container
class func getResolver() -> Resolver {
return self.sharedInstance.assembler.resolver
}
class func getContainer() -> Container {
return self.sharedInstance.container
}
private init() {
self.container = Container()
let assembler = Assembler([
LoginFactory()
])
self.assembler = assembler
}
}
LoginFactory class implements Assembly
internal class LoginFactory: Assembly {
func assemble(container: Container) {
container.register(LSViewModel.self) { res in
return LSViewModel()
}
container.register(LSCoordinator.self, factory: { res in
let lsc = LSCoordinator(window: AppDelegate.mainWindow!)
lsc.viewModel = res.resolve(LSViewModel.self)
return lsc
})
}
}
I read Assembly documentation where it says that it is better used for organization - https://github.com/Swinject/Swinject/blob/master/Documentation/Assembler.md. If I had not been using Assembly then I would have used commands like
DependencyManager.getContainer().register(LSViewModel.self) { _ in LSViewModel() }
DependencyManager.getContainer().register(LSCoordinator.self, factory: { (res) in
let lsc = LSCoordinator(window: AppDelegate.mainWindow!)
lsc.viewModel = res.resolve(LSViewModel.self)
return lsc
})
let lsCoordinator: LSCoordinator = DependencyManager.getContainer().resolve(LSCoordinator.self)!
Both the implementations are working as expected, without any crashes. I wonder why Assembly is even an advantage? I am doing the same thing without using Assembly without an overhead.
Benefits of organising your code using Assembly will start to out-weight the "no overhead" system once the project gets big enough. One way of keeping your code modularised and reusable is (amongst other stuff):
Keep DI code for different features separately
Features do not have the knowledge of how the final app is put together (e.g. DependencyManager)
IMO 1. is quite intuitive. 2. less so, but is free when using assemblies and gives you a lot of flexibility - for example you might want to reuse the feature in multiple apps (e.g. Networking).
Using assemblies you would have something like
let assembler = Assembler([
LoginAssembly(), // each assembly contains DI code for one feature
HomepageAssembly(),
UserProfileAssembly(),
// ...
])
while using the intuitive approach might get quite messy.
I want to use UI testing for a game using SKSpriteKit.
As my first tries did not work I wonder if it possible to use Xcode UI Testing with SpriteKit.
The main idea is to create the accessibility material for elements that you want to UI test. That's mean:
List all accessible elements contained in the scene
Configure settings for each of these elements, especially framedata.
Step by Step
This answer is for Swift 3 and is mainly based on Accessibility (Voice Over) with Sprite Kit
Let's say I want to make the SpriteKit button named tapMe accessible.
List of accessible elements.
Add an array of UIAccessibilityElementto the Scene.
var accessibleElements: [UIAccessibilityElement] = []
Scene's cycle life
I need to update two methods: didMove(to:)and willMove(from:).
override func didMove(to view: SKView) {
isAccessibilityElement = false
tapMe.isAccessibilityElement = true
}
As scene is the accessibility controller, documentation stated it must return False to isAccessibilityElement.
And:
override func willMove(from view: SKView) {
accessibleElements.removeAll()
}
Override UIAccessibilityContainer methods
3 methods are involved: accessibilityElementCount(), accessibilityElement(at index:) and index(ofAccessibilityElement. Please allow me to introduce an initAccessibility() method I'll describe later.
override func accessibilityElementCount() -> Int {
initAccessibility()
return accessibleElements.count
}
override func accessibilityElement(at index: Int) -> Any? {
initAccessibility()
if (index < accessibleElements.count) {
return accessibleElements[index]
} else {
return nil
}
}
override func index(ofAccessibilityElement element: Any) -> Int {
initAccessibility()
return accessibleElements.index(of: element as! UIAccessibilityElement)!
}
Initialize accessibility for the Scene
func initAccessibility() {
if accessibleElements.count == 0 {
// 1.
let elementForTapMe = UIAccessibilityElement(accessibilityContainer: self.view!)
// 2.
var frameForTapMe = tapMe.frame
// From Scene to View
frameForTapMe.origin = (view?.convert(frameForTapMe.origin, from: self))!
// Don't forget origins are different for SpriteKit and UIKit:
// - SpriteKit is bottom/left
// - UIKit is top/left
// y
// ┌────┐ ▲
// │ │ │ x
// ◉────┘ └──▶
//
// x
// ◉────┐ ┌──▶
// │ │ │
// └────┘ y ▼
//
// Thus before the following conversion, origin value indicate the bottom/left edge of the frame.
// We then need to move it to top/left by retrieving the height of the frame.
//
frameForTapMe.origin.y = frameForTapMe.origin.y - frameForTapMe.size.height
// 3.
elementForTapMe.accessibilityLabel = "tap Me"
elementForTapMe.accessibilityFrame = frameForTapMe
elementForTapMe.accessibilityTraits = UIAccessibilityTraitButton
// 4.
accessibleElements.append(elementForTapMe)
}
}
Create UIAccessibilityElement for tapMe
Compute frame data on device's coordinates. Don't forget that frame's origin is the top/left corner for UIKit
Set data for UIAccessibilityElement
Add this UIAccessibilityElement to list of all accessible elements in scene.
Now tapMe is accessible from UI testing perspective.
References
Session 406, UI Testing in Xcode, WWDC 2015
eyes off eyes on — Voiceover accessibility in SpriteKit
How do I support VoiceOver in a SpriteKit game? | Apple Developer Forums
swift - Accessibility (Voice Over) with Sprite Kit - Stack Overflow
According to Apple developer forum discussion, Integrate UITest with SpriteKit is not currently possible:
This is likely not currently possible, but it probably could be
Update 2017-02-19
According to comment by #ChrisLivdahl this may be achieved by using UIAccessibility — Session 406, UI Testing in Xcode, WWDC 2015.
The idea is to make the element needed UI Testable.
Update early 2021
A much shorter solution tested with SpriteKit, SwiftUI App and Swift 5.4
The earlier approaches didn't seem to work anymore when using XCUI Tests. The basis of my app is a SwiftUI app that has a SKScene as its main view. In order for it to work it was at the end quite simple actually and required much less steps for me to work.
1. Deactivate accessibility of the scene by adding only one line to the didMove() method
override func didMove(to view: SKView) {
isAccessibilityElement = false
}
As mentioned in Dominique Vial's answer
2. Conforming your nodes to UIAccessibilityIdentification protocol
class YourNode: SKSpriteNode, UIAccessibilityIdentification {
var accessibilityIdentifier: String?
//...
}
Mentioned here
Any node that needs to be accessible by the UI Test needs to conform to this protocol. Update Extension, instead of subclassing every node I'm using now the extension below.
3. Assign the accessibilityIdentifier and activate accessibility of your nodes objects.
let yourNode = YourNode()
yourNode.isAccessibilityElement = true
yourNode.accessibilityIdentifier = "nodeID"
4. That's it! Run your tests!
func testingNodes() throws {
app = XCUIApplication()
app.launch()
let node = app.otherElements["nodeID"]
XCTAssert(node.waitForExistence(timeout: 1))
}
5. Optional: set accessibilityTraits
yourNode.accessibilityTraits = [.button, .updatesFrequently]
let nodeAsButton = app.buttons["nodeID"]
You can set specific trait like .button to tell the accessibility API what your node is. This is useful to differentiate your nodes better during testing but also if you're planning to implement actual accessibility features for the user than this should be set correctly for it to work.
Update 1 - Using Extensions:
Instead of subclassing the nodes I'm using now the following extension on SKNode. I now only set the accessibilityLabel an no longer the accessibilityIdentifier
extension SKNode: UIAccessibilityIdentification {
public var accessibilityIdentifier: String? {
get {
super.accessibilityLabel
}
set(accessibilityIdentifier) {
super.accessibilityLabel = accessibilityIdentifier
}
}
}
Update 2 - Making all descendants of SKScene accessible
To allow for nodes and all of their descendants to be accessible I've ended up using the following extension on SKNode. This adds each node to the scene's accessibilityElements.
extension SKNode: UIAccessibilityIdentification {
public var accessibilityIdentifier: String? {
get {
super.accessibilityLabel
}
set(accessibilityIdentifier) {
super.accessibilityLabel = accessibilityIdentifier
}
}
func makeUITestAccessible(label: String, traits: UIAccessibilityTraits) {
accessibilityLabel = label
isAccessibilityElement = true
accessibilityTraits = traits
if let scene = scene {
if scene.accessibilityElements == nil {
scene.accessibilityElements = [self]
} else {
scene.accessibilityElements?.append(self)
}
}
}
}
I've implemented an example project based on your (#Domsware's) awesome answer, and I've confirmed this trick works well for both Xcode UI Testing Framework and KIF.
Hope this example helps for anyone who is interested in this topic :)
I have a need to sync two web views, so that anything that happens in one web view happens simultaneously in the other.
I have tried various ways, without success, and the more I try the more convoluted and likely bug ridden this is getting.
I feel there maybe a very simple way to do this, but can not figure it out.
One of the things that I note is not allowed is having the same NSTextField as the "takeStringURLFrom"
override func controlTextDidEndEditing(obj: NSNotification) {
webViewLeft.takeStringURLFrom(hiddenTextField)
webViewRight.takeStringURLFrom(urlField)
}
override func webView(sender: WebView!, didCommitLoadForFrame frame: WebFrame!) {
if frame == sender.mainFrame {
urlField.stringValue = sender.mainFrameURL
hiddenTextField.stringValue = sender.mainFrameURL
webViewRight.takeStringURLFrom(urlField)
webViewLeft.takeStringURLFrom(hiddenTextField)
printLn("realised just creating an infinite loop here")
}
}
I don't like this but it appears to work as needed. I think it needs some refinement. Using a hidden text field to mimic the url for the second web view and tie each web view to there respective text fields via the web view's referenced action "takeStringUrlFrom"
override func webView(sender: WebView!, didStartProvisionalLoadForFrame frame: WebFrame!) {
if frame == sender.mainFrame {
urlField.stringValue = sender.mainFrameURL
hiddenTextField.stringValue = sender.mainFrameURL
window.makeFirstResponder(nil)
window.makeFirstResponder(hiddenTextField)
window.makeFirstResponder(nil)
window.makeFirstResponder(urlField)
window.makeFirstResponder(nil)
}
}