How can I verify a class method is called using XCTAssert?
I have a service class, I would like to assert 2 things
- A method is called
- The correct params are passed to that method
Here is my class
protocol OAuthServiceProtocol {
func initAuthCodeFlow() -> Void
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void
}
class OAuthService: OAuthServiceProtocol {
fileprivate let apiClient: APIClient
init(apiClient: APIClient) {
self.apiClient = apiClient
}
func initAuthCodeFlow() -> Void {
}
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void {
}
}
Here are my tests
class OAuthServiceTests: XCTestCase {
var mockAPIClient: APIClient!
var mockURLSession: MockURLSession!
var sut: OAuthService!
override func setUp() {
mockAPIClient = APIClient()
mockAPIClient.session = MockURLSession(data: nil, urlResponse: nil, error: nil)
sut = OAuthService(apiClient: mockAPIClient)
}
func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")
class OAuthServiceMock: OAuthService {
override func initAuthCodeFlow() -> Void {
}
override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
renderOAuthWebViewExpectation.fulfill()
}
}
}
}
I was hoping to create a local sub class of OAuthService, assign that as my sut
and call something like like sut.initAuthCodeFlow()
and then assert that my expectation was fulfilled.
I believe this should satisfy point 1. However I cannot access my expectation when attempting to assign it as fulfilled as I get the following error
Class declaration cannot close over value
'renderOAuthWebViewExpectation' defined in outer scope
How can I mark this as fulfilled?
I am following a TDD approach, so I understand my OAuthService would produce a failing test at this point anyway*
swift unit-testing tdd xctestcase
add a comment |
I have a service class, I would like to assert 2 things
- A method is called
- The correct params are passed to that method
Here is my class
protocol OAuthServiceProtocol {
func initAuthCodeFlow() -> Void
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void
}
class OAuthService: OAuthServiceProtocol {
fileprivate let apiClient: APIClient
init(apiClient: APIClient) {
self.apiClient = apiClient
}
func initAuthCodeFlow() -> Void {
}
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void {
}
}
Here are my tests
class OAuthServiceTests: XCTestCase {
var mockAPIClient: APIClient!
var mockURLSession: MockURLSession!
var sut: OAuthService!
override func setUp() {
mockAPIClient = APIClient()
mockAPIClient.session = MockURLSession(data: nil, urlResponse: nil, error: nil)
sut = OAuthService(apiClient: mockAPIClient)
}
func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")
class OAuthServiceMock: OAuthService {
override func initAuthCodeFlow() -> Void {
}
override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
renderOAuthWebViewExpectation.fulfill()
}
}
}
}
I was hoping to create a local sub class of OAuthService, assign that as my sut
and call something like like sut.initAuthCodeFlow()
and then assert that my expectation was fulfilled.
I believe this should satisfy point 1. However I cannot access my expectation when attempting to assign it as fulfilled as I get the following error
Class declaration cannot close over value
'renderOAuthWebViewExpectation' defined in outer scope
How can I mark this as fulfilled?
I am following a TDD approach, so I understand my OAuthService would produce a failing test at this point anyway*
swift unit-testing tdd xctestcase
add a comment |
I have a service class, I would like to assert 2 things
- A method is called
- The correct params are passed to that method
Here is my class
protocol OAuthServiceProtocol {
func initAuthCodeFlow() -> Void
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void
}
class OAuthService: OAuthServiceProtocol {
fileprivate let apiClient: APIClient
init(apiClient: APIClient) {
self.apiClient = apiClient
}
func initAuthCodeFlow() -> Void {
}
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void {
}
}
Here are my tests
class OAuthServiceTests: XCTestCase {
var mockAPIClient: APIClient!
var mockURLSession: MockURLSession!
var sut: OAuthService!
override func setUp() {
mockAPIClient = APIClient()
mockAPIClient.session = MockURLSession(data: nil, urlResponse: nil, error: nil)
sut = OAuthService(apiClient: mockAPIClient)
}
func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")
class OAuthServiceMock: OAuthService {
override func initAuthCodeFlow() -> Void {
}
override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
renderOAuthWebViewExpectation.fulfill()
}
}
}
}
I was hoping to create a local sub class of OAuthService, assign that as my sut
and call something like like sut.initAuthCodeFlow()
and then assert that my expectation was fulfilled.
I believe this should satisfy point 1. However I cannot access my expectation when attempting to assign it as fulfilled as I get the following error
Class declaration cannot close over value
'renderOAuthWebViewExpectation' defined in outer scope
How can I mark this as fulfilled?
I am following a TDD approach, so I understand my OAuthService would produce a failing test at this point anyway*
swift unit-testing tdd xctestcase
I have a service class, I would like to assert 2 things
- A method is called
- The correct params are passed to that method
Here is my class
protocol OAuthServiceProtocol {
func initAuthCodeFlow() -> Void
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void
}
class OAuthService: OAuthServiceProtocol {
fileprivate let apiClient: APIClient
init(apiClient: APIClient) {
self.apiClient = apiClient
}
func initAuthCodeFlow() -> Void {
}
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void {
}
}
Here are my tests
class OAuthServiceTests: XCTestCase {
var mockAPIClient: APIClient!
var mockURLSession: MockURLSession!
var sut: OAuthService!
override func setUp() {
mockAPIClient = APIClient()
mockAPIClient.session = MockURLSession(data: nil, urlResponse: nil, error: nil)
sut = OAuthService(apiClient: mockAPIClient)
}
func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")
class OAuthServiceMock: OAuthService {
override func initAuthCodeFlow() -> Void {
}
override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
renderOAuthWebViewExpectation.fulfill()
}
}
}
}
I was hoping to create a local sub class of OAuthService, assign that as my sut
and call something like like sut.initAuthCodeFlow()
and then assert that my expectation was fulfilled.
I believe this should satisfy point 1. However I cannot access my expectation when attempting to assign it as fulfilled as I get the following error
Class declaration cannot close over value
'renderOAuthWebViewExpectation' defined in outer scope
How can I mark this as fulfilled?
I am following a TDD approach, so I understand my OAuthService would produce a failing test at this point anyway*
swift unit-testing tdd xctestcase
swift unit-testing tdd xctestcase
asked Jan 1 at 8:54
Harry BlueHarry Blue
777824
777824
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
Create a property on your mock, mutating it's value within the method you expect to call. You can then use your XCTAssertEqual to check that prop has been updated.
func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")
class OAuthServiceMock: OAuthService {
var renderOAuthWebViewExpectation: XCTestExpectation!
var didCallRenderOAuthWebView = false
override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
didCallRenderOAuthWebView = true
renderOAuthWebViewExpectation.fulfill()
}
}
let sut = OAuthServiceMock(apiClient: mockAPIClient)
XCTAssertEqual(sut.didCallRenderOAuthWebView, false)
sut.renderOAuthWebViewExpectation = renderOAuthWebViewExpectation
sut.initAuthCodeFlow()
waitForExpectations(timeout: 1) { _ in
XCTAssertEqual(sut.didCallRenderOAuthWebView, true)
}
}
add a comment |
I was hoping to create a local sub class of
OAuthService
, assign that as my sut and call something like likesut.initAuthCodeFlow()
and then assert that my expectation was fulfilled.
I would strongly discourage you from using this approach. If your SUT is an instance of the subclass then your test is not truly testing OAuthService
, but OAuthService
mock.
Moreover, if we think of tests as a tool to:
- prevent bugs when code is change
- help refactoring and maintenance of the code
then I would argue that testing that calling a certain function calls another function is not a good test. That's harsh, I know, so let me unpack why that's the case.
The only thing it's testing is that initAuthCodeFlow()
calls renderOAuthWebView(forService:, queryitems:)
under the hood. It doesn't have any assertion on the actual behaviour of the system under test, on the outputs it produces directly or not. If I were to edit the implementation of renderOAuthWebView(forService:, queryitems:)
and add some code that would crash at runtime this test would not fail.
A test like this doesn't help with keeping the codebase easy to change, because if you want to change the implementation of OAuthService
, maybe by adding a parameter to renderOAuthWebView(forService:, queryitems:)
or by renaming queryitems
into queryItems
to match the capitalization, you'll have to update both the production code and the test. In other words, the test will get in your way of refactoring -changing how the code looks without changing how it behaves- without any extra benefit.
So, how should one test OAuthService
in a way that prevents bugs and helps moving fast? The trick is all in testing the behaviour instead of the implementation.
What should OAuthService
do? initAuthCodeFlow()
doesn't return any value, so we can check for direct outputs, but we can still check indirect outputs, side effects.
I'm making a guess here, but I from your test checking that renderOAuthWebView(forService:, queryitems:)
I'd and the fact that it gets an APIClient
type as input I'd say it'll present some kind of web view for a certain URL, and then make another request to the given APIClient
maybe with the OAuth token received from the web view?
A way to test the interaction with APIClient
is to make an assertion for the expected endpoint to be called. You can do it with a tool like OHHTTPStubs or with your a custom test double for URLSession
that records the requests it gets and allows you to check them.
As for the presentation of the web view, you can use the delegate patter for it, and set a test double conforming to the delegate protocol which records whether it's called or not. Or you could test at a higher level and inspect the UIWindow
in which the test are running to see if the root view controller is the one with the web view.
At the end of the day is all a matter of trade offs. The approach you've taken is not wrong, it just optimizes more towards asserting the code implementation rather than its behaviour. I hope that with this answer I showed a different kind of optimization, one biased towards the behaviour. In my experience this style of testing proves more helpful in the medium-long run.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53994172%2fhow-can-i-verify-a-class-method-is-called-using-xctassert%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
Create a property on your mock, mutating it's value within the method you expect to call. You can then use your XCTAssertEqual to check that prop has been updated.
func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")
class OAuthServiceMock: OAuthService {
var renderOAuthWebViewExpectation: XCTestExpectation!
var didCallRenderOAuthWebView = false
override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
didCallRenderOAuthWebView = true
renderOAuthWebViewExpectation.fulfill()
}
}
let sut = OAuthServiceMock(apiClient: mockAPIClient)
XCTAssertEqual(sut.didCallRenderOAuthWebView, false)
sut.renderOAuthWebViewExpectation = renderOAuthWebViewExpectation
sut.initAuthCodeFlow()
waitForExpectations(timeout: 1) { _ in
XCTAssertEqual(sut.didCallRenderOAuthWebView, true)
}
}
add a comment |
Create a property on your mock, mutating it's value within the method you expect to call. You can then use your XCTAssertEqual to check that prop has been updated.
func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")
class OAuthServiceMock: OAuthService {
var renderOAuthWebViewExpectation: XCTestExpectation!
var didCallRenderOAuthWebView = false
override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
didCallRenderOAuthWebView = true
renderOAuthWebViewExpectation.fulfill()
}
}
let sut = OAuthServiceMock(apiClient: mockAPIClient)
XCTAssertEqual(sut.didCallRenderOAuthWebView, false)
sut.renderOAuthWebViewExpectation = renderOAuthWebViewExpectation
sut.initAuthCodeFlow()
waitForExpectations(timeout: 1) { _ in
XCTAssertEqual(sut.didCallRenderOAuthWebView, true)
}
}
add a comment |
Create a property on your mock, mutating it's value within the method you expect to call. You can then use your XCTAssertEqual to check that prop has been updated.
func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")
class OAuthServiceMock: OAuthService {
var renderOAuthWebViewExpectation: XCTestExpectation!
var didCallRenderOAuthWebView = false
override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
didCallRenderOAuthWebView = true
renderOAuthWebViewExpectation.fulfill()
}
}
let sut = OAuthServiceMock(apiClient: mockAPIClient)
XCTAssertEqual(sut.didCallRenderOAuthWebView, false)
sut.renderOAuthWebViewExpectation = renderOAuthWebViewExpectation
sut.initAuthCodeFlow()
waitForExpectations(timeout: 1) { _ in
XCTAssertEqual(sut.didCallRenderOAuthWebView, true)
}
}
Create a property on your mock, mutating it's value within the method you expect to call. You can then use your XCTAssertEqual to check that prop has been updated.
func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")
class OAuthServiceMock: OAuthService {
var renderOAuthWebViewExpectation: XCTestExpectation!
var didCallRenderOAuthWebView = false
override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
didCallRenderOAuthWebView = true
renderOAuthWebViewExpectation.fulfill()
}
}
let sut = OAuthServiceMock(apiClient: mockAPIClient)
XCTAssertEqual(sut.didCallRenderOAuthWebView, false)
sut.renderOAuthWebViewExpectation = renderOAuthWebViewExpectation
sut.initAuthCodeFlow()
waitForExpectations(timeout: 1) { _ in
XCTAssertEqual(sut.didCallRenderOAuthWebView, true)
}
}
answered Jan 1 at 9:19
nodediggitynodediggity
74717
74717
add a comment |
add a comment |
I was hoping to create a local sub class of
OAuthService
, assign that as my sut and call something like likesut.initAuthCodeFlow()
and then assert that my expectation was fulfilled.
I would strongly discourage you from using this approach. If your SUT is an instance of the subclass then your test is not truly testing OAuthService
, but OAuthService
mock.
Moreover, if we think of tests as a tool to:
- prevent bugs when code is change
- help refactoring and maintenance of the code
then I would argue that testing that calling a certain function calls another function is not a good test. That's harsh, I know, so let me unpack why that's the case.
The only thing it's testing is that initAuthCodeFlow()
calls renderOAuthWebView(forService:, queryitems:)
under the hood. It doesn't have any assertion on the actual behaviour of the system under test, on the outputs it produces directly or not. If I were to edit the implementation of renderOAuthWebView(forService:, queryitems:)
and add some code that would crash at runtime this test would not fail.
A test like this doesn't help with keeping the codebase easy to change, because if you want to change the implementation of OAuthService
, maybe by adding a parameter to renderOAuthWebView(forService:, queryitems:)
or by renaming queryitems
into queryItems
to match the capitalization, you'll have to update both the production code and the test. In other words, the test will get in your way of refactoring -changing how the code looks without changing how it behaves- without any extra benefit.
So, how should one test OAuthService
in a way that prevents bugs and helps moving fast? The trick is all in testing the behaviour instead of the implementation.
What should OAuthService
do? initAuthCodeFlow()
doesn't return any value, so we can check for direct outputs, but we can still check indirect outputs, side effects.
I'm making a guess here, but I from your test checking that renderOAuthWebView(forService:, queryitems:)
I'd and the fact that it gets an APIClient
type as input I'd say it'll present some kind of web view for a certain URL, and then make another request to the given APIClient
maybe with the OAuth token received from the web view?
A way to test the interaction with APIClient
is to make an assertion for the expected endpoint to be called. You can do it with a tool like OHHTTPStubs or with your a custom test double for URLSession
that records the requests it gets and allows you to check them.
As for the presentation of the web view, you can use the delegate patter for it, and set a test double conforming to the delegate protocol which records whether it's called or not. Or you could test at a higher level and inspect the UIWindow
in which the test are running to see if the root view controller is the one with the web view.
At the end of the day is all a matter of trade offs. The approach you've taken is not wrong, it just optimizes more towards asserting the code implementation rather than its behaviour. I hope that with this answer I showed a different kind of optimization, one biased towards the behaviour. In my experience this style of testing proves more helpful in the medium-long run.
add a comment |
I was hoping to create a local sub class of
OAuthService
, assign that as my sut and call something like likesut.initAuthCodeFlow()
and then assert that my expectation was fulfilled.
I would strongly discourage you from using this approach. If your SUT is an instance of the subclass then your test is not truly testing OAuthService
, but OAuthService
mock.
Moreover, if we think of tests as a tool to:
- prevent bugs when code is change
- help refactoring and maintenance of the code
then I would argue that testing that calling a certain function calls another function is not a good test. That's harsh, I know, so let me unpack why that's the case.
The only thing it's testing is that initAuthCodeFlow()
calls renderOAuthWebView(forService:, queryitems:)
under the hood. It doesn't have any assertion on the actual behaviour of the system under test, on the outputs it produces directly or not. If I were to edit the implementation of renderOAuthWebView(forService:, queryitems:)
and add some code that would crash at runtime this test would not fail.
A test like this doesn't help with keeping the codebase easy to change, because if you want to change the implementation of OAuthService
, maybe by adding a parameter to renderOAuthWebView(forService:, queryitems:)
or by renaming queryitems
into queryItems
to match the capitalization, you'll have to update both the production code and the test. In other words, the test will get in your way of refactoring -changing how the code looks without changing how it behaves- without any extra benefit.
So, how should one test OAuthService
in a way that prevents bugs and helps moving fast? The trick is all in testing the behaviour instead of the implementation.
What should OAuthService
do? initAuthCodeFlow()
doesn't return any value, so we can check for direct outputs, but we can still check indirect outputs, side effects.
I'm making a guess here, but I from your test checking that renderOAuthWebView(forService:, queryitems:)
I'd and the fact that it gets an APIClient
type as input I'd say it'll present some kind of web view for a certain URL, and then make another request to the given APIClient
maybe with the OAuth token received from the web view?
A way to test the interaction with APIClient
is to make an assertion for the expected endpoint to be called. You can do it with a tool like OHHTTPStubs or with your a custom test double for URLSession
that records the requests it gets and allows you to check them.
As for the presentation of the web view, you can use the delegate patter for it, and set a test double conforming to the delegate protocol which records whether it's called or not. Or you could test at a higher level and inspect the UIWindow
in which the test are running to see if the root view controller is the one with the web view.
At the end of the day is all a matter of trade offs. The approach you've taken is not wrong, it just optimizes more towards asserting the code implementation rather than its behaviour. I hope that with this answer I showed a different kind of optimization, one biased towards the behaviour. In my experience this style of testing proves more helpful in the medium-long run.
add a comment |
I was hoping to create a local sub class of
OAuthService
, assign that as my sut and call something like likesut.initAuthCodeFlow()
and then assert that my expectation was fulfilled.
I would strongly discourage you from using this approach. If your SUT is an instance of the subclass then your test is not truly testing OAuthService
, but OAuthService
mock.
Moreover, if we think of tests as a tool to:
- prevent bugs when code is change
- help refactoring and maintenance of the code
then I would argue that testing that calling a certain function calls another function is not a good test. That's harsh, I know, so let me unpack why that's the case.
The only thing it's testing is that initAuthCodeFlow()
calls renderOAuthWebView(forService:, queryitems:)
under the hood. It doesn't have any assertion on the actual behaviour of the system under test, on the outputs it produces directly or not. If I were to edit the implementation of renderOAuthWebView(forService:, queryitems:)
and add some code that would crash at runtime this test would not fail.
A test like this doesn't help with keeping the codebase easy to change, because if you want to change the implementation of OAuthService
, maybe by adding a parameter to renderOAuthWebView(forService:, queryitems:)
or by renaming queryitems
into queryItems
to match the capitalization, you'll have to update both the production code and the test. In other words, the test will get in your way of refactoring -changing how the code looks without changing how it behaves- without any extra benefit.
So, how should one test OAuthService
in a way that prevents bugs and helps moving fast? The trick is all in testing the behaviour instead of the implementation.
What should OAuthService
do? initAuthCodeFlow()
doesn't return any value, so we can check for direct outputs, but we can still check indirect outputs, side effects.
I'm making a guess here, but I from your test checking that renderOAuthWebView(forService:, queryitems:)
I'd and the fact that it gets an APIClient
type as input I'd say it'll present some kind of web view for a certain URL, and then make another request to the given APIClient
maybe with the OAuth token received from the web view?
A way to test the interaction with APIClient
is to make an assertion for the expected endpoint to be called. You can do it with a tool like OHHTTPStubs or with your a custom test double for URLSession
that records the requests it gets and allows you to check them.
As for the presentation of the web view, you can use the delegate patter for it, and set a test double conforming to the delegate protocol which records whether it's called or not. Or you could test at a higher level and inspect the UIWindow
in which the test are running to see if the root view controller is the one with the web view.
At the end of the day is all a matter of trade offs. The approach you've taken is not wrong, it just optimizes more towards asserting the code implementation rather than its behaviour. I hope that with this answer I showed a different kind of optimization, one biased towards the behaviour. In my experience this style of testing proves more helpful in the medium-long run.
I was hoping to create a local sub class of
OAuthService
, assign that as my sut and call something like likesut.initAuthCodeFlow()
and then assert that my expectation was fulfilled.
I would strongly discourage you from using this approach. If your SUT is an instance of the subclass then your test is not truly testing OAuthService
, but OAuthService
mock.
Moreover, if we think of tests as a tool to:
- prevent bugs when code is change
- help refactoring and maintenance of the code
then I would argue that testing that calling a certain function calls another function is not a good test. That's harsh, I know, so let me unpack why that's the case.
The only thing it's testing is that initAuthCodeFlow()
calls renderOAuthWebView(forService:, queryitems:)
under the hood. It doesn't have any assertion on the actual behaviour of the system under test, on the outputs it produces directly or not. If I were to edit the implementation of renderOAuthWebView(forService:, queryitems:)
and add some code that would crash at runtime this test would not fail.
A test like this doesn't help with keeping the codebase easy to change, because if you want to change the implementation of OAuthService
, maybe by adding a parameter to renderOAuthWebView(forService:, queryitems:)
or by renaming queryitems
into queryItems
to match the capitalization, you'll have to update both the production code and the test. In other words, the test will get in your way of refactoring -changing how the code looks without changing how it behaves- without any extra benefit.
So, how should one test OAuthService
in a way that prevents bugs and helps moving fast? The trick is all in testing the behaviour instead of the implementation.
What should OAuthService
do? initAuthCodeFlow()
doesn't return any value, so we can check for direct outputs, but we can still check indirect outputs, side effects.
I'm making a guess here, but I from your test checking that renderOAuthWebView(forService:, queryitems:)
I'd and the fact that it gets an APIClient
type as input I'd say it'll present some kind of web view for a certain URL, and then make another request to the given APIClient
maybe with the OAuth token received from the web view?
A way to test the interaction with APIClient
is to make an assertion for the expected endpoint to be called. You can do it with a tool like OHHTTPStubs or with your a custom test double for URLSession
that records the requests it gets and allows you to check them.
As for the presentation of the web view, you can use the delegate patter for it, and set a test double conforming to the delegate protocol which records whether it's called or not. Or you could test at a higher level and inspect the UIWindow
in which the test are running to see if the root view controller is the one with the web view.
At the end of the day is all a matter of trade offs. The approach you've taken is not wrong, it just optimizes more towards asserting the code implementation rather than its behaviour. I hope that with this answer I showed a different kind of optimization, one biased towards the behaviour. In my experience this style of testing proves more helpful in the medium-long run.
answered Feb 15 at 10:06
mokagiomokagio
8,45223243
8,45223243
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53994172%2fhow-can-i-verify-a-class-method-is-called-using-xctassert%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown