8

Say I'm writing a unit test for a tableView:cellForRowAtIndexPath: delegate method on a view controller. This method could return a couple of different configurations of cells depending on the index path I pass in.

I can easily assert on the cell.textLabel.text property. But how can I assert that the cell.imageView.image property contains the correct image? Neither the image or the imageView have (public API) properties I can use to find out the image name or file name.

The best I've come up with is creating the smallest possible valid .png (using [UIImage imageWithData:] so I don't touch the disk in my unit tests) and asserting the byte array I get from cell.imageView.image is the one I expect. I've created an OCHamcrest matcher to make this a little nicer but it's an unsatisfying and inflexible approach.

Has anyone got a better idea?

Robert Atkins
  • 23,528
  • 15
  • 68
  • 97

4 Answers4

7

If you're using [UIImage imagedNamed:], the images are cached. Excerpt from the UIImage class reference for imagedNamed::

This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method loads the image data from the specified file, caches it, and then returns the resulting object.

This means you can assert on cell.imageView.image == [UIImage imagedName:@"my_image"] as this is a pointer comparison and since the images are cached multiple calls to imageNamed: with the same name will return the same pointer. Even if memory gets tight, you should still be fine, as the UIImage class reference explains:

In low-memory situations, image data may be purged from a UIImage object to free up memory on the system. This purging behavior affects only the image data stored internally by the UIImage object and not the object itself.

neilco
  • 7,964
  • 2
  • 36
  • 41
  • Oh wow, I didn't realise that. Awesome answer. The one thing that's stopped me from accepting this right away is using `[UIImage imageNamed:]` implies disk access and I'd like to avoid that in my unit tests if I can. – Robert Atkins Oct 11 '13 at 08:00
  • There's no disk, but there will be a slight overhead from file system access if the requested image is a cache miss. If it's a cache hit, then it's a memory read. – neilco Oct 11 '13 at 08:16
  • This appears to no longer work as of Xcode 6.4. I had to use `XCTAssertEqualObjects(image, [UIImage imageNamed:@"myImage"])` instead. – Isaac Overacker Oct 01 '15 at 18:30
3

Converting the images to Data and then comparing the Data. Since the image is just a pointer to memory location.

guard let data1 = image1?.pngData(), let data2 = image2.pngData() else {
    XCTFail("Data should not be nil")
    return
}
XCTAssertEqual(data1, data2)

swift5

Vinoth Anandan
  • 1,157
  • 17
  • 15
  • 2
    While this code snippet may solve the question, [including an explanation](http://meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – Gerhard Jan 14 '21 at 07:14
  • This is the best answer. – ScottyBlades Mar 21 '22 at 03:39
2

You can compare the contents of UIImage directly using the isEqual method on a UIImage which will compare that the two images are like for like.

So in your tests, you can do:

let expectedImage = UIImage(named: "my_picture")
let returnedImage = SomeImageReturnedFromFunction()
XCTAssertEqualObjects(expectedImage, returnedImage) // will return true if SomeImageReturnedFromFunction returns my_picture

Reference: https://developer.apple.com/documentation/uikit/uiimage#overview

micnguyen
  • 1,419
  • 18
  • 25
0

You can indeed compare with the "isEqual" method but rather like this :

let expectedImage = UIImage(named: "an_image")
let returnedImage = myFunctionRet() // optional ?

XCTAssert(returnedImage?.isEqual(expectedImage) == true)
Medhi
  • 2,656
  • 23
  • 16