-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Blog service lookup blog #19366
Blog service lookup blog #19366
Conversation
blog(with: NSPredicate(format: "visible = YES"), in: context) | ||
} | ||
|
||
private static func blog(with predicate: NSPredicate, in context: NSManagedObjectContext) -> Blog? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is translated from BlogService.blogsWithPredicate
You can test the changes in Jetpack from this Pull Request by:
|
You can test the changes in WordPress from this Pull Request by:
|
private static func blog(with predicate: NSPredicate, in context: NSManagedObjectContext) -> Blog? { | ||
let request = NSFetchRequest<Blog>(entityName: NSStringFromClass(Blog.self)) | ||
request.includesSubentities = false | ||
request.predicate = predicate | ||
request.fetchLimit = 1 | ||
request.sortDescriptors = [NSSortDescriptor(key: "settings.name", ascending: true)] | ||
|
||
do { | ||
return try context.fetch(request).first | ||
} catch { | ||
DDLogError("Couldn't fetch blogs with predicate \(predicate): \(error)") | ||
return nil | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe in a future iteration it would be cool to throw
or return Result<Blog, Error>
.
Of course, that's out of scope now and also would require making a choice over which approach to use. Just thinking out loud.
/** | ||
Returns the blog currently flagged as the one last used, or the primary blog, | ||
or the first blog in an alphanumerically sorted list that supports the given | ||
feature, whichever is found first. | ||
*/ | ||
- (nullable Blog *)lastUsedOrFirstBlogThatSupports:(BlogFeature)feature; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method hasn't been converted. But the code builds without it, so it was clearly unused 😄 👍
private class MockViewController: UIViewController { | ||
|
||
var presentExpectation: XCTestExpectation? | ||
|
||
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { | ||
presentExpectation?.fulfill() | ||
super.present(viewControllerToPresent, animated: flag, completion: completion) | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see this being used in other tests, too. (I'm not actually sure there are other use cases, but I assume this mustn't be the only test calling present...
.
How would you feel about making it "stricter"? I.e. require the expectation to be set, as that's the whole point why one would use this UIViewController
subclass.
It could be done by throw
ing if presentExpectation
is nil
. Or, we could provide a new, dedicated initializer, although, I always forget whether you can make the other ones private
when working with UIViewController
subclasses.
Another idea could be to move the whole expectation logic within the object, and make it expose a method to verify the presentation occurred. That would also be truer to the "mock" name, defined in the xunit patterns as a test double that verifies "indirect outputs of SUT" by verifying "correctness against expectations". (See my thoughts on test double naming here).
let presenterMock = PresenterMockViewController()
...
presenter.presentReblog(blogService: blogService!, readerPost: readerPost!, origin: presenterMock)
...
presenterMock.expectPresentationToOccurr()
// In PresenterMockViewController
func expectPresentationToOccurr(file: ... = #file, line: ... = #line) {
guard let presentExpectation = self.presentExpectation else { /* fail */ }
waitForExpectations([presentExpectation], timeout: ...) { error in
guard let error = error else { return }
XCTFail("present was not called", file: file, line: line)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with your thinking here—it should do what a "mock" type does if it's called a "mock". I tried to come up with some other ways to do this, like using OCMock(I couldn't get it to work in a Swift code), or capture the presented view controller and make assertions on it in the test function. But eventually I decided to go with what I had here, simply because that's the pattern used in the test class here (also a few other test classes that I ran into).
I do worry about having mixed patterns, especially if they are in the same file. Others(or myself in 6 months) may not know which pattern to follow when they make changes to this file. I'd prefer that we spend dedicate time to investigate alternative options than the "custom mock type with XCTExpectation
" pattern used here, and update the whole test with the new pattern. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer that we spend dedicate time to investigate alternative options than the "custom mock type with XCTExpectation" pattern used here, and update the whole test [unit tests suite?] with the new pattern.
That would be very appropriate. It would also gives us a chance to see how the verify the different use cases.
Others(or myself in 6 months)
So true! I don't trust my future self's memory either 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tracked in #19373
/// Returns the blog currently flagged as the one last used, or the primary blog, | ||
/// or the first blog in an alphanumerically sorted list, whichever is found first. | ||
@objc(lastUsedOrFirstBlogInContext:) | ||
static func lastUsedOrFirstBlog(in context: NSManagedObjectContext) -> Blog? { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't think this while reading the method name, but, after seeing this in action, I wonder if we can do without the "Blog".
Without it, the usage would be:
Blog.lastUsedOrFirst(in: context)
That reads fine to me, and it seems pretty clear that the return value would be a Blog
.
What do you think? I'm not too bothered by it, just an idea. But, if you like the suggestion, I think we should document it somewhere as the desired style for these ActiveRecord-like methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. The "Blog" does seem redundant. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-iterating my approval.
There were a couple of build steps that failed but I don't think they were related to these changes. I restarted them.
This PR moves some Blog history query functions from
BlogService
to aBlog
extension.Again, the new code (the extension) in this PR is translated from Objective-C code. I don't see big risk in it. Most changes in this PR are switching to call the new API. An outlier is e0ebe8f, I noticed the
blog
argument is not used anywhere, so I removed it, along with a Core Data query.Regression Notes
Potential unintended areas of impact
None.
What I did to test those areas of impact (or what existing automated tests I relied on)
None.
What automated tests I added (or what prevented me from doing so)
None.
PR submission checklist:
RELEASE-NOTES.txt
if necessary.