[GH-ISSUE #718] Writing integration tests for spotipy-based app #432

Closed
opened 2026-02-27 23:22:36 +03:00 by kerem · 10 comments
Owner

Originally created by @joebonneau on GitHub (Aug 19, 2021).
Original GitHub issue: https://github.com/spotipy-dev/spotipy/issues/718

Hi there,

I'm writing integration tests for a CLI tool and I'm trying to determine the best way to structure my tests. For things like testing whether a search was run successfully, it makes sense to test the response itself.

But what about testing something like whether next_track was successful? I haven't been able to figure out whether I can extract the response code, so I would need to, instead, first make a call to current_playback then call it again and test whether the responses differ.

Of course, I could always just test whether something like a SpotifyException is raised, but I'm not sure I would encounter this error unless my authentication was unsuccessful. I do hit this error when there is no active device and device_id isn't passed in, but I'm thinking I could get some wonky results in my CI builds if there happens to be an active device and I didn't realize before running the build.

So I guess my questions are:

  1. Can I access the response code from the Spotify object after making a call?
  2. If not, how might you suggest I structure my tests?

Thanks!

Originally created by @joebonneau on GitHub (Aug 19, 2021). Original GitHub issue: https://github.com/spotipy-dev/spotipy/issues/718 <!--- Please make sure you've: - read the FAQ https://github.com/plamere/spotipy/blob/master/FAQ.md - read the documentation https://spotipy.readthedocs.io/en/latest/ - searched older issues If your question is about code, please share the code you are using ---> Hi there, I'm writing integration tests for a CLI tool and I'm trying to determine the best way to structure my tests. For things like testing whether a search was run successfully, it makes sense to test the response itself. But what about testing something like whether `next_track` was successful? I haven't been able to figure out whether I can extract the response code, so I would need to, instead, first make a call to `current_playback` then call it again and test whether the responses differ. Of course, I could always just test whether something like a `SpotifyException` is raised, but I'm not sure I would encounter this error unless my authentication was unsuccessful. I do hit this error when there is no active device and `device_id` isn't passed in, but I'm thinking I could get some wonky results in my CI builds if there happens to be an active device and I didn't realize before running the build. So I guess my questions are: 1. Can I access the response code from the `Spotify` object after making a call? 2. If not, how might you suggest I structure my tests? Thanks!
kerem 2026-02-27 23:22:36 +03:00
  • closed this issue
  • added the
    question
    label
Author
Owner

@stephanebruckert commented on GitHub (Aug 19, 2021):

For things like testing whether a search was run successfully, it makes sense to test the response itself.

Agree!

But what about testing something like whether next_track was successful?

Why would that be different, I believe it would also directly send a response? For example, show playlist tracks, do "next" once, and verify that you get the second page?

Of course, I could always just test whether something like a SpotifyException is raised, but I'm not sure I would encounter this error unless my authentication was unsuccessful.

Okay in this case you would like to force spotipy to fail. Have you heard of mocking? It can help you fake the result of methods you don't have access to. Since the spotipy object is the object of a class, you could search for "instance method mock python", here are some ideas https://stackoverflow.com/q/5036920/1515819

<!-- gh-comment-id:902051983 --> @stephanebruckert commented on GitHub (Aug 19, 2021): > For things like testing whether a search was run successfully, it makes sense to test the response itself. Agree! > But what about testing something like whether next_track was successful? Why would that be different, I believe it would also directly send a response? For example, show playlist tracks, do "next" once, and verify that you get the second page? > Of course, I could always just test whether something like a SpotifyException is raised, but I'm not sure I would encounter this error unless my authentication was unsuccessful. Okay in this case you would like to force spotipy to fail. Have you heard of mocking? It can help you fake the result of methods you don't have access to. Since the spotipy object is the object of a class, you could search for "instance method mock python", here are some ideas https://stackoverflow.com/q/5036920/1515819
Author
Owner

@Peter-Schorn commented on GitHub (Aug 19, 2021):

But what about testing something like whether next_track was successful? I haven't been able to figure out whether I can extract the response code

spotipy does not always return the response code. If the underlying API request returns a non-successful status code, then the method (e.g., Spotify.next_track) will raise an exception (which may contain the http status code in it), which you'll probably want to interpret as a test failure. If a successful status code is returned, then an exception will not be raised. For the purposes of your tests, this is all the information you need.

but I'm not sure I would encounter this error unless my authentication was unsuccessful.

There are plenty of other error conditions that are unrelated to authentication. For example, if you provide an invalid id to a method such as Spotify.track, then an exception will be raised.

<!-- gh-comment-id:902151032 --> @Peter-Schorn commented on GitHub (Aug 19, 2021): > But what about testing something like whether next_track was successful? I haven't been able to figure out whether I can extract the response code spotipy does not always return the response code. If the underlying API request returns a non-successful status code, then the method (e.g., `Spotify.next_track`) will raise an exception (which may contain the http status code in it), which you'll probably want to interpret as a test failure. If a successful status code is returned, then an exception will not be raised. For the purposes of your tests, this is all the information you need. > but I'm not sure I would encounter this error unless my authentication was unsuccessful. There are plenty of other error conditions that are unrelated to authentication. For example, if you provide an invalid id to a method such as `Spotify.track`, then an exception will be raised.
Author
Owner

@joebonneau commented on GitHub (Aug 20, 2021):

Thanks for the input @stephanebruckert and @Peter-Schorn ! I think that I've got my head on my shoulders now as far as approaching the problem goes. I am still quite new to the concept of mocking (though it's making more and more sense as I go) and still wrapping my head around when I actually need to use it.

But what about testing something like whether next_track was successful?

Why would that be different, I believe it would also directly send a response? For example, show playlist tracks, do "next" once, and verify that you get the second page?

My thought here was that a method like Spotify.search() returns data in the response but Spotify.next_track() does not and I was unsure how to handle that. But as Peter mentioned, an exception will be raised if the method is unsuccessful and that should be sufficient for my purposes.

<!-- gh-comment-id:902391633 --> @joebonneau commented on GitHub (Aug 20, 2021): Thanks for the input @stephanebruckert and @Peter-Schorn ! I think that I've got my head on my shoulders now as far as approaching the problem goes. I am still quite new to the concept of mocking (though it's making more and more sense as I go) and still wrapping my head around when I actually need to use it. > But what about testing something like whether next_track was successful? >>Why would that be different, I believe it would also directly send a response? For example, show playlist tracks, do "next" once, and verify that you get the second page? My thought here was that a method like `Spotify.search()` returns data in the response but `Spotify.next_track()` does not and I was unsure how to handle that. But as Peter mentioned, an exception will be raised if the method is unsuccessful and that should be sufficient for my purposes.
Author
Owner

@joebonneau commented on GitHub (Aug 21, 2021):

So I was able to write the integration tests but I'm now running into another issue that perhaps one of you has experience with. In CI, my integration tests seem to get held up at the step where the authentication is made and I've narrowed it down to the point were the redirect URI is opened in the browser. In the CI environment, the browser can't open, but also not sure how to automatically enter in the URL (I saw somewhere you can get the URL using urllib3).

I know this a bit out of scope, but if you have any thoughts, it would be appreciated!

<!-- gh-comment-id:903125060 --> @joebonneau commented on GitHub (Aug 21, 2021): So I was able to write the integration tests but I'm now running into another issue that perhaps one of you has experience with. In CI, my integration tests seem to get held up at the step where the authentication is made and I've narrowed it down to the point were the redirect URI is opened in the browser. In the CI environment, the browser can't open, but also not sure how to automatically enter in the URL (I saw somewhere you can get the URL using `urllib3`). I know this a bit out of scope, but if you have any thoughts, it would be appreciated!
Author
Owner

@Peter-Schorn commented on GitHub (Aug 21, 2021):

Don't test the authorization process. Authorize your app in advance and then save the token info in persistent storage.

<!-- gh-comment-id:903129261 --> @Peter-Schorn commented on GitHub (Aug 21, 2021): Don't test the authorization process. Authorize your app in advance and then save the token info in persistent storage.
Author
Owner

@joebonneau commented on GitHub (Aug 21, 2021):

Don't test the authorization process. Authorize your app in advance and then save the token info in persistent storage.

I've already authorized the app on my local machine and the tests run just fine, so I'm trying to work through what this might look like. I assume that you don't mean to commit the .cache file that is generated? I'd assume that would be as bad as committing the CLIENT_SECRET, but I may be thinking about this incorrectly.

<!-- gh-comment-id:903136323 --> @joebonneau commented on GitHub (Aug 21, 2021): > Don't test the authorization process. Authorize your app in advance and then save the token info in persistent storage. I've already authorized the app on my local machine and the tests run just fine, so I'm trying to work through what this might look like. I assume that you don't mean to commit the `.cache` file that is generated? I'd assume that would be as bad as committing the `CLIENT_SECRET`, but I may be thinking about this incorrectly.
Author
Owner

@Peter-Schorn commented on GitHub (Aug 21, 2021):

Yes, committing the cache file to git is as bad as committing the client secret. But there are ways of securely storing the token info. JSON is just a string, so you can store this string as a secret environment variable, just like your client secret. If you're using github actions to run your tests, then you can use a repository secret.

<!-- gh-comment-id:903138356 --> @Peter-Schorn commented on GitHub (Aug 21, 2021): Yes, committing the cache file to git is as bad as committing the client secret. But there are ways of securely storing the token info. JSON is just a string, so you can store this string as a secret environment variable, just like your client secret. If you're using github actions to run your tests, then you can use a [repository secret](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository).
Author
Owner

@joebonneau commented on GitHub (Aug 21, 2021):

Ah, very cool! I'm using Travis CI, but I'm thinking something like this might work:

Define the token info as an environment variable TOKEN_INFO, then:

auth = sp.Spotify(
               auth_manager=SpotifyOAuth(
                   scope=scope,
                   client_id=client_id,
                   client_secret=client_secret,
                   redirect_uri=redirect_uri,
                   cache_handler=CacheFileHandler(
                       cache_path=TOKEN_INFO
                  )
       )
)

I'll give it a go sometime this weekend, but let me know if this seems like a reasonable approach. Thanks again for all of the help, it's really invaluable for a new dev like myself.

<!-- gh-comment-id:903140377 --> @joebonneau commented on GitHub (Aug 21, 2021): Ah, very cool! I'm using Travis CI, but I'm thinking something like this might work: Define the token info as an environment variable `TOKEN_INFO`, then: ```spotify auth = sp.Spotify( auth_manager=SpotifyOAuth( scope=scope, client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, cache_handler=CacheFileHandler( cache_path=TOKEN_INFO ) ) ) ``` I'll give it a go sometime this weekend, but let me know if this seems like a reasonable approach. Thanks again for all of the help, it's really invaluable for a new dev like myself.
Author
Owner

@Peter-Schorn commented on GitHub (Aug 21, 2021):

That won't work. cache_path should be a file path. CacheFileHandler reads and writes to a file. You should use MemoryCacheHandler instead. Make sure to convert the JSON string to a python dict before passing it in to MemoryCacheHandler.__init__.

<!-- gh-comment-id:903146533 --> @Peter-Schorn commented on GitHub (Aug 21, 2021): That won't work. `cache_path` should be a file path. `CacheFileHandler` reads and writes to a file. You should use `MemoryCacheHandler` instead. Make sure to convert the JSON string to a python dict before passing it in to `MemoryCacheHandler.__init__`.
Author
Owner

@joebonneau commented on GitHub (Aug 21, 2021):

Finally got it! Phew!

<!-- gh-comment-id:903180395 --> @joebonneau commented on GitHub (Aug 21, 2021): Finally got it! Phew!
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/spotipy#432
No description provided.