I wrote a GithubClient
class which is basically a wrapper for Ruby's Octokit
library.
It contains one public method aside from the constructor, called push
, which basically creates commit in the remote.
I have made the instance variables parameters to the constructor and then provided defaults, so I can inject a mock.
It has private methods to create commits and push commit trees which uses Octokit
, and I think I'm using it in a standard way. I copied most of the code online.
class GithubClient
def initialize(
octokit_client=Octokit::Client.new(:access_token => ENV['GITHUB_WRITE_TOKEN']),
repo_name='main',
ref_name='heads/master'
)
@client = octokit_client
@repo = repo_name
@ref = ref_name
end
def push(underwriter, files)
sha_new_commit = create_new_commit(underwriter, files)
push_commit_tree(sha_new_commit)
end
private
def create_new_commit(underwriter, files)
sha_latest_commit = @client.ref(@repo, @ref).object.sha
sha_base_tree = @client.commit(@repo, sha_latest_commit).commit.tree.sha
new_commit_tree = files.map do |file|
blob_sha = @client.create_blob(@repo, file)
{
:path => file.git_path_name,
:mode => "100644",
:type => "blob",
:sha => blob_sha,
}
end
sha_new_tree = @client.create_tree(@repo, new_commit_tree, {:base_tree => sha_base_tree }).sha
commit_message = "commit - #{files.first.name}"
sha_new_commit = @client.create_commit(@repo, commit_message, sha_new_tree, sha_latest_commit).sha
end
def push_commit_tree(sha_new_commit)
@client.update_ref(@repo, @ref, sha_new_commit)
end
end
Then I wrote a mock test for this, as shown below
def test_octokit
octokit_client = mock()
octokit_client.stubs(:ref).returns(stub(:object => stub(:sha => "sha1")))
octokit_client.stubs(:commit).returns(stub(:commit => stub(:tree => stub(:sha => "sha2"))))
octokit_client.stubs(:create_blob).returns("blob sha")
octokit_client.stubs(:create_commit).returns(stub(:sha => "new commit sha"))
octokit_client.expects(:create_tree).with("repo_name", [{:path => "", :mode => "100644", :type => "blob", :sha => "blob sha"}], {:base_tree => "sha2"}).once.returns(stub(:sha => "sha3"))
octokit_client.expects(:update_ref).with("repo_name", "ref_name", "new commit sha")
github_client = Undertaker::Mapper::GithubClient.new(octokit_client, 'repo_name', 'ref_name')
github_client.push('', [FakeFile.new])
end
It does test that .create_tree
and .create_commit
are called once with expected parameters.
However, because the implementation of GithubClient has so many nested invocations (e.g. .commit.tree.sha
), I had to stub all those calls. It's very messy and worries me.
Also, even though I'm only calling push
in my test, my stubbing assumes knowledge of calls made in private methods and I feel like I am testing implementation detail.
Am I doing this right?
Also, what needs to be tested in a class like my GithubClient
?
https://api.github.com/repos/...
usingWebMock
in another test case. Do I need to test it when I am not directly making a call to the API? I think it's the responsibility of the writer ofOctokit
to unit test those calls, but I just wanted to make sure.