Reputation: 8635
I'm trying to stub a function using sinon. The function has the following signature
export function getIndexDocument(
svc: MetaHTTPService | ServiceConfig
): MetaPromise<RepoResponseResult<IndexDocument>> {
Is this the right way to sub it
sandbox.stub(getIndexDocument).resolves({} as RepoResponseResult)
I tried that but it returns an error.
Here's how this function is called.
I have a class called AssetsController with the following functions
public async exploreIndexDocument(): Promise<Asset | undefined> {
// it makes an HTTP request and returns a promise that resolves with the following info { repoId: "", assetId: "" }
const {
result: { assignedDirectories }
} = await getIndexDocument(this.serviceConfig).catch(err => {
throw new Error(`Bad repsonse`);
});
return {
repoId: result.repoId;
assetId: result.assetId
}
}
public async function copyAsset(asset) {
const res = await this.exploreIndexDocument();
const repoId = res.repoId;
return asset.copy(repoId);
}
I'm trying to test the function copyAsset, but it calls exploreIndexDocument which calls getIndexDocument. getIndexDocument is imported at the top of the file and lives in the module @ma/http. getIndexDocument makes an HTTP request.
How can I test copyAsset given that it calls getIndexDocument which makes an HTTP request?
Upvotes: 1
Views: 2360
Reputation: 2097
I think most of your problems can be solved by revisiting your architecture. For example, instead of creating an explicit dependency on getIndexDocument
within your AssetController
class you can simply inject it in. This will allow you to swap implementations depending on the context.
type IndexDocumentProvider = (svc: MetaHTTPService | ServiceConfig) => MetaPromise<RepoResponseResult<IndexDocument>>;
interface AssetControllerOptions {
indexDocumentProvider: IndexDocumentProvider
}
class AssetController {
private _getIndexDocument: IndexDocumentProvider;
public constructor(options: AssetControllerOptions) {
this._getIndexDocument = options.indexDocumentProvider;
}
}
Then you can use this._getIndexDocument
wherever and not worry about how to make the original implementation behave like you want in your tests. You can simply provide an implementation that does whatever you'd like.
describe('copyAsset', () => {
it('fails on index document error.', () => {
const controller = new AssetController({
indexDocumentProvider: () => Promise.reject(new Error(':('));
});
....
});
it('copies asset using repo id.', () => {
const controller = new AssetController({
indexDocumentProvider: () => Promise.resolve({ repoId: "420" })
});
...
});
});
You can obviously use stubs
instead of just functions or whatever if you need something fancy.
Above we removed an explicit dependency to an implementation and instead replaced it with a contract that must be provided to the controller. The is typically called Inversion of Control
and Dependency Injection
Upvotes: 1
Reputation: 186994
According to the docs, you can't stub an existing function.
You can:
// Create an anonymous sstub function
var stub = sinon.stub();
// Replaces object.method with a stub function. An exception is thrown
// if the property is not already a function.
var stub = sinon.stub(object, "method");
// Stubs all the object’s methods.
var stub = sinon.stub(obj);
What you can't do is stub just a function like:
var stub = sinon.stub(myFunctionHere);
This makes sense because if all you have is a reference to a function, then you can just create a new function to use instead, and then pass that into where ever your test needs it to go.
I think you just want:
const myStub = sandbox.stub().resolves({} as RepoResponseResult)
In your update it sounds like you want to put the stub on the AssetsController
class. See this answer for more info on that, but in this case I think you want:
const myStub = sandbox
.stub(AssetsController.prototype, 'exploreIndexDocument')
.resolves({} as RepoResponseResult)
Now anytime an instance of AssetsController
calls its exploreIndexDocument
method, the stub should be used instead.
Upvotes: 1