Skip to content
This repository has been archived by the owner on Jan 17, 2019. It is now read-only.

Use more appropriate asserts #259

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

cerihughes
Copy link
Contributor

Always use XCTAssertEqualObjects when comparing objects for equality. XCTAssertEqual was causing some issues with NSIndexPath equality on 32 bit architectures (possibly because 64-bit architectures pool instances that are equal?)

@spotify-ci-bot
Copy link

spotify-ci-bot commented Dec 22, 2016

3 Messages
📖 Executed 339 tests, with 0 failures (0 unexpected) in 7.275 (8.472) seconds
📖 Executed 336 tests, with 0 failures (0 unexpected) in 7.320 (8.682) seconds
📖 Executed 9 tests, with 0 failures (0 unexpected) in 147.943 (147.952) seconds

Generated by 🚫 danger

@codecov-io
Copy link

codecov-io commented Dec 22, 2016

Current coverage is 93.43% (diff: 100%)

Merging #259 into master will increase coverage by 0.07%

@@             master       #259   diff @@
==========================================
  Files            72         72          
  Lines          5015       5015          
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits           4682       4686     +4   
+ Misses          333        329     -4   
  Partials          0          0          

Powered by Codecov. Last update b9852cb...bb26062

@cerihughes
Copy link
Contributor Author

@spotify/objc-dev

Copy link
Contributor

@JohnSundell JohnSundell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the time we actually want to make sure that the same instance of an object is used, not just that they're equal. So I'd update only the index path cases that were failing (and should use XCTAssertEqualObjects).

@@ -58,7 +58,7 @@ - (void)testRegisteringFactoryAndCreatingAction
HUBActionFactoryMock * const factory = [[HUBActionFactoryMock alloc] initWithActions:@{actionIdentifier.namePart: action}];
[self.actionRegistry registerActionFactory:factory forNamespace:actionIdentifier.namespacePart];

XCTAssertEqual([self.actionRegistry createCustomActionForIdentifier:actionIdentifier], action);
XCTAssertEqualObjects([self.actionRegistry createCustomActionForIdentifier:actionIdentifier], action);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we actually want to make sure that the objects are the same instance so XCTAssertEqual should be used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should use XCTAssertTrue([self.actionRegistry createCustomActionForIdentifier:actionIdentifier] == action) for that then, just to be explicit about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hamcrest has a "same instance" matcher that is good for these scenarios. XCTest doesn't seem to though :(

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it more explicit to use XCTAssertEqual that does exactly that? 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, possibly. I have a problem with XCTAssertEqual as it's often misused, and people often aren't asserting what they think. If you do want to test instance equality, then using XCTAssertEqual just feels wrong as actually tests "more" than equality.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are testing instance equality XCTAssertTrue(a == b) is far more explicit. XCTAssertEqual is just too vague and has different behaviour depending on what you pass it, and that irks me :)

XCTAssertEqual([self.actionRegistry createCustomActionForIdentifier:actionIdentifier], action);

// These should be the same instance.
XCTAssertTrue([self.actionRegistry createCustomActionForIdentifier:actionIdentifier] == action);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really fail to understand why this is better than XCTAssertEqual which is meant for exactly these type of comparisons.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with Ceri; XCTAssertEqual is a smell for non-scalar types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's because in a test, if I look at a line that says XCTAssertEqual(objA, obj2), I immediately think "do we really want to check equality here, or is this a typo".

Admittedly XCTAssertTrue(obj1 == obj2) doesn't do a great deal to fix that ambiguity, which is why I like Hamcrest's approach of providing a "same instance" matcher for objects so that it's obvious by looking at it whether you want to check for equality or instance equivalence.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the comment is the most useful thing here. I'll revert the assert and keep the comment.

Copy link
Contributor

@rastersize rastersize Jan 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding a description to the assert? That is:

XCTAssertTrue([self.actionRegistry createCustomActionForIdentifier:actionIdentifier] == action,
              @"The action should be the same instance as the factory mock vends");

That way the comment will be directly attached to the assert. Making sure it also shows up when the test fails.

@cerihughes
Copy link
Contributor Author

@JohnSundell : I would assume that by default we'd want to be checking object equality in tests, unless we have a requirement that something returns the same instance under certain conditions. Only then would we want to test the actual instances.

The NSIndexPath case is a good example - on some platforms (for some reason) calls to factory methods will return different instances, and on other platforms they return the same instance (I haven't verified this, but it's one explanation for what's happening). Ultimately though, it shouldn't matter, as long as we use object equality, not instance equality as isEqualTo: will take care of all the differences for us.

Only fixing the cases with NSIndexPath because we know there are problems with that right now seems a bit broken, as the same issue could arise with other classes in future OS releases (as an example).

@JensAyton
Copy link
Contributor

@cerihughes NSIndexPaths of two elements are tagged pointers on 64-bit systems. Same sort of fun happens with short strings.

@@ -108,8 +108,8 @@ - (void)testTwoSubsequentRenders
[self waitForExpectationsWithTimeout:2 handler:nil];

XCTAssertEqual([self.collectionViewLayout numberOfInvocations], 2u);
XCTAssertEqualObjects([self.collectionViewLayout capturedViewModelAtIndex:0], firstViewModel);
XCTAssertEqualObjects([self.collectionViewLayout capturedViewModelAtIndex:1], secondViewModel);
XCTAssertEqual([self.collectionViewLayout capturedViewModelAtIndex:0], firstViewModel);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn’t we use XCTAssertTrue([self.collectionViewLayout capturedViewModelAtIndex:0] == firstViewModel) here as well? As well as adding an assert description stating that we really do want to check for instance equality.

Just like at https://github.com/spotify/HubFramework/pull/259/files#diff-51a93e5f57aabdf96df5027e93c75d58R62

XCTAssertEqualObjects([self.collectionViewLayout capturedViewModelAtIndex:0], firstViewModel);
XCTAssertEqualObjects([self.collectionViewLayout capturedViewModelAtIndex:1], secondViewModel);
XCTAssertEqual([self.collectionViewLayout capturedViewModelAtIndex:0], firstViewModel);
XCTAssertEqual([self.collectionViewLayout capturedViewModelAtIndex:1], secondViewModel);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rastersize
Copy link
Contributor

@cerihughes I’m in favor of the changes made in this PR. Unless we explicitly want to check that two objects are the same instance we should use XCTAssertEqualObjects(obj1, obj2, desc, …).

Further I think we should, like you’re doing here, use XCTAssertTrue() with a description (or comment) when we actually want to check for instance equality. I’d also be fine with adding a function or macro like HUBAssertEqualInstances(ins1, ins2, desc, …) to make the code more self-explanatory.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
6 participants