I need the test status after each test case is executed in my test suite in Xcode. I know an observer can help in achieving it. But how do I use it in my tests?
You are on the right track and can achieve what you're wanting to do via the XCTestObservation protocol (https://developer.apple.com/documentation/xctest/xctestobservation). You can add an observer to your test case class and I'd recommend doing this in the setUp() method since it gets executed before each test method.
override func setUp() {
super.setUp()
continueAfterFailure = false
XCUIApplication().launch()
XCTestObservationCenter.shared.addTestObserver(UITestObserver())
}
To do this you should implement a class that conforms to the XCTestObservation protocol and then provide your own implementation to the methods of interest to perform whatever actions you need/want. In your case you're probably going to want to provide an implementation for this method...
optional public func testCase(_ testCase: XCTestCase, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: Int)
Here's a quick example of what this test observer class might look like...
import XCTest
public class UITestObserver: NSObject, XCTestObservation {
public func testCase(_ testCase: XCTestCase,
didFailWithDescription description: String,
inFile filePath: String?,
atLine lineNumber: Int) {
print("failure description: \(description)")
print("failed test case: \(testCase)")
if let filePath = filePath {
print("failure at file path: \(filePath)")
}
print("failure at line: \(lineNumber)")
}
}
This function I provided an example of above gets called whenever one of your test cases fails, so you don't need to "do" anything from within your test case class or methods.
Hope this helps!
The result of each test case executed is saved on a file named ***TestSummeries.plist.
You will find it under
~/Library/Developer/Xcode/DerivedData/<your-app-name>/Logs/Test/****_TestSummeries.plist
If you run your test many times just delete the everything inside DerivedData before execution. Then you will find only one TestSummeries.plist.
Then parse the plist and get your desired data from the plist file.
** If you need more information about it feel free to comment below.
Related
Since Xcode 10.1(maybe 10) when I create a Unit test file I don't have calls super.tearDown() and super.setUp() .
I've not seen such changes in release notes.
In documentation https://developer.apple.com/documentation/xctest/xctestcase/understanding_setup_and_teardown_for_test_methods are still here.
So my question should I still write super.tearDown() and super.setUp()?
class SomethingTests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
For a direct subclass of XCTestCase, there never was any change of behavior for not calling super.setUp(). That's because setUp and tearDown are template methods with empty implementations at the top level.
Though there's no change in behavior, omitting the calls to super means that if you create a test hierarchy with more than one level, you'll have to add them back.
When would you ever have more than one level? There are two cases:
When you want to reuse the same tests for different scenarios.
When you subclass XCTestCase to make a customized helper.
These don't happen every day. But they do happen. Deciding "I need it here, but I don't need it there" is perilous. So I'd just call super all the time.
I have a data class A with a function as follows:
data class A(val a: String) {
fun foo(b: String) = "$a, $b"
}
I attempt the following mock in my test:
fun `whatever`() {
val spy = spyk<A>()
every { spy.a } returns "Tree"
assertThat(spy.foo("Snake")).isEqualTo("Tree Snake")
}
When I run a test written like this it fails with a NullPointerException on the line fun foo... in the data class.
Am I doing anything wrong or is this a bug in MockK?
I have totally different results when I run your code. Firstly it complains that there is no default constructor.
Then I fixed it to use the non-default constructor and it prints "abc Snake"
val spy = spyk(A("abc"))
every { spy.a } returns "Tree"
println(spy.foo("Snake"))
There is a reason for that. Kotlin is accessing a property through a field in foo function. This seems to be an optimization.
MockK is not able to do anything about it right now. There is the following ticket to transform getfield call: https://github.com/mockk/mockk/issues/104
I want to use Fastlane Snapshot in order to generate screenshots for my app. But the behavior of the app is different when launch for the first time and launching after that.
How do I manage to get a consistent behavior in order to grab the screenshots?
(this question is also relevant for any UI test desired consistency I presume)
You should be using UserDefaults class to preserve data in your app so that you can stub data in your tests.
If we assume that the Bool key you use to see if it's the first launch is isFirstTime, in order to stub it in your UI test, you should pass it to launchArguments following the value YES or NO (for Bool values). Note that I added - sign before key, this is the way it works:
class FirstTimeLaunchTest: XCTestCase {
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments += ["-isFirstTime", "YES"]
app.launch()
}
func testWelcomeScreenShown() {
XCTAssert(app.navigationBars["Welcome"].exists)
}
}
For tests where you'd like to have first start skipped, use this class:
class LaterLaunchesTest: XCTestCase {
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments += ["-isFirstTime", "NO"]
app.launch()
}
func testMainAppScreenShown() {
XCTAssert(app.navigationBars["My App"].exists)
}
}
One note though: If you are using SwiftyUserDefaults library, this solution wouldn't work. There is a problem in the current version of the library where converting YES and NO strings to true and false is not working as expected. There was a PR that solved this problem (that is rejected), but to solve this problem, you can look at the solutions #2 and #3 from this answer.
I've been experimenting with Swift on my way home from WWDC. One of the most compelling new features of Swift, in my opinion, was namespacing. I haven't managed to get it to work as I expected it should though. Please see the attached screenshot and let me know if you have an idea of what I'm doing wrong.
EDIT: I have of course tried to remove the import statement.
Turns out that this is a known bug: https://devforums.apple.com/message/976286#976286
I am sorry, if I search for "namespace" or "namespacing" in the Swift-eBook there are no results. Maybe you can give me a link to an additional resource?
I would solve your problem simply with a struct and static functions.
ClassA
struct ClassA {
static func localize() {
println("Localized String")
}
}
ClassB
You can drop the import and execute the ClassA-function as follows:
ClassA.localize()
The second way
In your case you can also just make an extension of String like so:
extension String {
func localize() -> String {
return self+"-localized"
}
}
println("Test".localize()) // prints "Test-localized"
EDIT: Please let me be clear, I'm asking how to do this in Grails using Spring Dependency Injection, and NOT Grails' metaclass functionality or new().
I have a grails service that is for analyzing log files. Inside the service I use the current time for lots of things. For unit testing I have several example log files that I parse with this service. These have times in them obviously.
I want my service, DURING UNIT TESTING to think that the current time is no more than a few hours after the last logging statement in my example log files.
So, I'm willing to this:
class MyService {
def currentDate = { -> new Date() }
def doSomeStuff() {
// need to know when is "right now"
Date now = currentDate()
}
}
So, what I want to be able to do is have currentDate injected or set to be some other HARDCODED time, like
currentDate = { -> new Date(1308619647140) }
Is there not a way to do this with some mockWhatever method inside my unit test? This kind of stuff was super easy with Google Guice, but I have no idea how to do it in Spring.
It's pretty frustrating that when I Google "grails dependency injection" all I find are examples of
class SomeController {
// wow look how amazing this is, it's injected automatically!!
// isn't spring incredible OMG!
def myService
}
It feels like all that's showing me is that I don't have to type new ...()
Where do I tell it that when environment equals test, then do this:
currentDate = { -> new Date(1308619647140) }
Am I just stuck setting this property manually in my test??
I would prefer not to have to create a "timeService" because this seems silly considering I just want 1 tiny change.
Groovy is a dynamic language, and as such it allows you to do almost what you're asking for:
class MyServiceTests extends GrailsUnitTestCase {
def testDoSomeStuff() {
def service = new MyService()
service.currentDate = { -> new Date(1308619647140) }
// assert something on service.doSomeStuff()
}
}
Keep in mind this only modifies the service instance, not the class. If you need to modify the class you'll need to work with the metaClass. Take a look at this post by mrhaki.
Another option would be to make the current date a parameter to doSomeStuff(). That way you wouldn't need to modify your service instance.
Thanks for the help guys. The best solution I could come up with for using Spring DI in this case was to do the following in
resources.groovy
These are the two solutions I found:
1: If I want the timeNowService to be swapped for testing purposes everywhere:
import grails.util.GrailsUtil
// Place your Spring DSL code here
beans = {
if (GrailsUtil.environment == 'test') {
println ">>> test env"
timeNowService(TimeNowMockService)
} else {
println ">>> not test env"
timeNowService(TimeNowService)
}
}
2: I could do this if I only want this change to apply to this particular service:
import grails.util.GrailsUtil
// Place your Spring DSL code here
beans = {
if (GrailsUtil.environment == 'test') {
println ">>> test env"
time1(TimeNowMockService)
} else {
println ">>> not test env"
time1(TimeNowService)
}
myService(MyService) {
diTest = 'hello 2'
timeNowService = ref('time1')
}
}
In either case I would use the service by calling
timeNowService.now().
The one strange, and very frustrating thing to me was that I could not do this:
import grails.util.GrailsUtil
// Place your Spring DSL code here
beans = {
if (GrailsUtil.environment == 'test') {
println ">>> test env"
myService(MyService) {
timeNow = { -> new Date(1308486447140) }
}
} else {
println ">>> not test env"
myService(MyService) {
timeNow = { -> new Date() }
}
}
}
In fact, when I tried that I also had a dummy value in there, like dummy = 'hello 2' and then a default value of dummy = 'hello' in the myService class itself. And when I did this 3rd example with the dummy value set in there as well, it silently failed to set, apparently b/c timeNow blew something up in private.
I would be interested to know if anyone could explain why this fails.
Thanks for the help guys and sorry to be impatient...
Since Groovy is dynamic, you could just take away your currentDate() method from your service and replace it by one that suits your need. You can do this at runtime during the setup of your test.
Prior to having an instance of MyService instantiated, have the following code executed:
MyService.metaClass.currentDate << {-> new Date(1308619647140) }
This way, you can have a consistent behavior across all your tests.
However, if you prefer, you can override the instance method by a closure that does the same trick.
Let me know how it goes.
Vincent Giguère