tests/testthat/test-channels.R

# ==============================================================================
# Tests for YouTube Channel Functions
# ==============================================================================
# This file tests all channel-related functions including:
# - get_all_channel_video_stats() - Get statistics for all videos in a channel
# - list_channel_resources() - Get channel information and playlists
# - get_playlist_items() - Used internally by channel stats functions
#
# Tests use accurate YouTube API v3 mocks that reflect current API behavior:
# - dislikeCount is private since December 2021
# - Proper response structure with kind, etag, pageInfo fields
# ==============================================================================

test_that("get_all_channel_video_stats handles video details correctly", {
  # Mock the required functions
  with_mocked_bindings(
    # Mock list_channel_resources
    list_channel_resources = function(...) {
      list(
        kind = "youtube#channelListResponse",
        etag = "test-etag",
        pageInfo = list(
          totalResults = 1,
          resultsPerPage = 1
        ),
        items = list(
          list(
            kind = "youtube#channel",
            etag = "channel-etag",
            id = "UCad-_hTvV-yBPcpy9jwQWeA",
            contentDetails = list(
              relatedPlaylists = list(
                uploads = "UUad-_hTvV-yBPcpy9jwQWeA",
                likes = "",
                watchHistory = "HL",
                watchLater = "WL"
              )
            )
          )
        )
      )
    },

    # Mock get_playlist_items to return 2 video IDs
    get_playlist_items = function(...) {
      list(
        kind = "youtube#playlistItemListResponse",
        etag = "test-etag",
        pageInfo = list(
          totalResults = 2,
          resultsPerPage = 2
        ),
        items = list(
          list(
            kind = "youtube#playlistItem",
            etag = "item1-etag",
            contentDetails = list(
              videoId = "video1",
              videoPublishedAt = "2024-01-01T00:00:00Z"
            )
          ),
          list(
            kind = "youtube#playlistItem",
            etag = "item2-etag",
            contentDetails = list(
              videoId = "video2",
              videoPublishedAt = "2024-01-02T00:00:00Z"
            )
          )
        ),
        nextPageToken = NULL
      )
    },

    # Mock tuber_GET for video stats and details
    tuber_GET = function(path, query, ...) {
      # Handle combined snippet,statistics call (what get_video_details does)
      if (grepl("statistics", query$part) && grepl("snippet", query$part)) {
        # Return both stats and snippet for videos
        list(
          kind = "youtube#videoListResponse",
          etag = "test-etag",
          pageInfo = list(
            totalResults = 2,
            resultsPerPage = 2
          ),
          items = list(
            list(
              kind = "youtube#video",
              etag = "video1-etag",
              id = "video1",
              snippet = list(
                publishedAt = "2024-01-01T00:00:00Z",
                channelId = "UCad-_hTvV-yBPcpy9jwQWeA",
                title = "Test Video 1",
                description = "Test description 1",
                channelTitle = "Test Channel"
              ),
              statistics = list(
                viewCount = "1000",
                likeCount = "100",
                # dislikeCount removed - private since Dec 2021
                commentCount = "50"
              )
            ),
            list(
              kind = "youtube#video",
              etag = "video2-etag",
              id = "video2",
              snippet = list(
                publishedAt = "2024-01-02T00:00:00Z",
                channelId = "UCad-_hTvV-yBPcpy9jwQWeA",
                title = "Test Video 2",
                description = "Test description 2",
                channelTitle = "Test Channel"
              ),
              statistics = list(
                viewCount = "2000",
                likeCount = "200",
                # dislikeCount removed - private since Dec 2021
                commentCount = "100"
              )
            )
          )
        )
      } else if (grepl("statistics", query$part)) {
        # Return stats for videos (fallback for individual calls)
        list(
          kind = "youtube#videoListResponse",
          etag = "test-etag",
          pageInfo = list(
            totalResults = 2,
            resultsPerPage = 2
          ),
          items = list(
            list(
              kind = "youtube#video",
              etag = "video1-etag",
              id = "video1",
              statistics = list(
                viewCount = "1000",
                likeCount = "100",
                # dislikeCount removed - private since Dec 2021
                commentCount = "50"
              )
            ),
            list(
              kind = "youtube#video",
              etag = "video2-etag",
              id = "video2",
              statistics = list(
                viewCount = "2000",
                likeCount = "200",
                # dislikeCount removed - private since Dec 2021
                commentCount = "100"
              )
            )
          )
        )
      } else if (grepl("snippet", query$part)) {
        # Return details for videos (fallback for individual calls)
        list(
          kind = "youtube#videoListResponse",
          etag = "test-etag",
          pageInfo = list(
            totalResults = 2,
            resultsPerPage = 2
          ),
          items = list(
            list(
              kind = "youtube#video",
              etag = "video1-etag",
              id = "video1",
              snippet = list(
                publishedAt = "2024-01-01T00:00:00Z",
                channelId = "UCad-_hTvV-yBPcpy9jwQWeA",
                title = "Test Video 1",
                description = "Test description 1",
                channelTitle = "Test Channel"
              )
            ),
            list(
              kind = "youtube#video",
              etag = "video2-etag",
              id = "video2",
              snippet = list(
                publishedAt = "2024-01-02T00:00:00Z",
                channelId = "UCad-_hTvV-yBPcpy9jwQWeA",
                title = "Test Video 2",
                description = "Test description 2",
                channelTitle = "Test Channel"
              )
            )
          )
        )
      }
    },

    {
      # Test the function
      result <- get_all_channel_video_stats(channel_id = "UCad-_hTvV-yBPcpy9jwQWeA")

      # Verify the result structure
      expect_true(is.data.frame(result))
      expect_equal(nrow(result), 2)

      # Check column names (note: dislike_count may be present but NA)
      expected_cols <- c("id", "title", "publication_date", "description",
                        "channel_id", "channel_title", "view_count",
                        "like_count", "comment_count", "url")
      expect_true(all(expected_cols %in% names(result)))
      # dislike_count column may exist but should be NA (private since Dec 2021)
      if ("dislike_count" %in% names(result)) {
        expect_true(all(is.na(result$dislike_count)))
      }

      # Verify data integrity
      expect_equal(result$id[1], "video1")
      expect_equal(result$id[2], "video2")
      expect_equal(result$title[1], "Test Video 1")
      expect_equal(result$title[2], "Test Video 2")
      expect_equal(result$publication_date[1], "2024-01-01T00:00:00Z")
      expect_equal(result$publication_date[2], "2024-01-02T00:00:00Z")
      expect_equal(result$view_count[1], "1000")
      expect_equal(result$view_count[2], "2000")

      # Check URL generation
      expect_equal(result$url[1], "https://www.youtube.com/watch?v=video1")
      expect_equal(result$url[2], "https://www.youtube.com/watch?v=video2")
    }
  )
})

test_that("get_all_channel_video_stats handles missing publishedAt field", {
  # Mock setup with video missing publishedAt
  with_mocked_bindings(
    list_channel_resources = function(...) {
      list(
        kind = "youtube#channelListResponse",
        etag = "test-etag",
        pageInfo = list(
          totalResults = 1,
          resultsPerPage = 1
        ),
        items = list(
          list(
            kind = "youtube#channel",
            etag = "channel-etag",
            id = "UCtest",
            contentDetails = list(
              relatedPlaylists = list(
                uploads = "UUtest",
                likes = "",
                watchHistory = "HL",
                watchLater = "WL"
              )
            )
          )
        )
      )
    },

    get_playlist_items = function(...) {
      list(
        kind = "youtube#playlistItemListResponse",
        etag = "test-etag",
        pageInfo = list(
          totalResults = 1,
          resultsPerPage = 1
        ),
        items = list(
          list(
            kind = "youtube#playlistItem",
            etag = "item1-etag",
            contentDetails = list(
              videoId = "video1",
              videoPublishedAt = "2024-01-01T00:00:00Z"
            )
          )
        ),
        nextPageToken = NULL
      )
    },

    tuber_GET = function(path, query, ...) {
      # Handle combined snippet,statistics call (what get_video_details does)
      if (grepl("statistics", query$part) && grepl("snippet", query$part)) {
        list(
          kind = "youtube#videoListResponse",
          etag = "test-etag",
          pageInfo = list(
            totalResults = 1,
            resultsPerPage = 1
          ),
          items = list(
            list(
              kind = "youtube#video",
              etag = "video1-etag",
              id = "video1",
              snippet = list(
                # publishedAt is intentionally missing for this test
                channelId = "UCtest",
                title = "Test Video",
                description = "Test",
                channelTitle = "Test"
              ),
              statistics = list(
                viewCount = "1000",
                likeCount = "100",
                # dislikeCount removed - private since Dec 2021
                commentCount = "25"
              )
            )
          )
        )
      } else if (grepl("statistics", query$part)) {
        list(
          kind = "youtube#videoListResponse",
          etag = "test-etag",
          pageInfo = list(
            totalResults = 1,
            resultsPerPage = 1
          ),
          items = list(
            list(
              kind = "youtube#video",
              etag = "video1-etag",
              id = "video1",
              statistics = list(
                viewCount = "1000",
                likeCount = "100",
                # dislikeCount removed - private since Dec 2021
                commentCount = "25"
              )
            )
          )
        )
      } else if (grepl("snippet", query$part)) {
        list(
          kind = "youtube#videoListResponse",
          etag = "test-etag",
          pageInfo = list(
            totalResults = 1,
            resultsPerPage = 1
          ),
          items = list(
            list(
              kind = "youtube#video",
              etag = "video1-etag",
              id = "video1",
              snippet = list(
                # publishedAt is intentionally missing for this test
                channelId = "UCtest",
                title = "Test Video",
                description = "Test",
                channelTitle = "Test"
              )
            )
          )
        )
      }
    },

    {
      result <- get_all_channel_video_stats(channel_id = "UCtest")

      # Should handle missing publishedAt gracefully with NA
      expect_true(is.data.frame(result))
      expect_equal(nrow(result), 1)
      expect_true(is.na(result$publication_date[1]))
    }
  )
})

Try the tuber package in your browser

Any scripts or data that you put into this service are public.

tuber documentation built on March 25, 2026, 9:08 a.m.