-
Notifications
You must be signed in to change notification settings - Fork 29.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unit testing extensions is quite difficult #94746
Comments
Adding @octref since you have been making good work here |
Our QE team at Red Hat has been doing great work with the https://github.com/redhat-developer/vscode-extension-tester if anybody wants to take a look at it. |
Prescribing one pattern excludes others. I don't think it's good to include "how you can write your extension so it's unit-testable". |
This originates from #82471. I never got around to responding and the issue got locked, sorry. Rearchitecturing my code to decouple it from vscode would be difficult, or at least take a while. I tried mocking but that was nightmarish. Getting the mocks to behave like vscode in some cases basically required rewriting vscode logic. Maybe if I tried a blend of all three approaches - decoupling what can easily be decoupled for easy unit testing, mocking that which is lightly coupled, and integration testing that which is heavily coupled, maybe that would work effectively. But I would strongly prefer plain old unit testing if it was possible. Is there any technical solution that might solve #6586 to let vscode be imported during unit tests? Maybe there's some way it might be achieved (or maybe it fundamentally goes against the core architecture, idk) If there's no possible way it can be realistically done then the only action item I can think of would be adding documentation / code samples. See microsoft/vscode-extension-samples#91 |
To my experience with LaTeX Workshop, the following steps are an easy way to write unit tests.
I do not think this is an appropriate way. However, it is super easy. |
@tamuratak thanks, but I believe those are integration tests - they run with a live vscode instance and it looks like you run them through vscode-test so you can't choose a individual test to run. I'm talking about unit tests - the kind where you could run individual tests through the test explorer sidepanel. Also just curious but why are you exporting objects that way? In arepl I just import the objects for my integration tests directly. |
While I was writing my above comment I realized that there are some more possibilities here. If there's a way to run vscode in headless mode the tests could be run faster and without the UI popping up. Unfortunately electron doesn't support headless mode yet 😢 (even though chrome supports it which is kinda weird). Another thing that would be nice is if you could run tests individually through the test explorer. |
To make unit testing easier, it would be nice to have certain classes and enums from the extension API to be available without VSCode itself. Like, for example, This has already been done for |
Of the top of my head I can think of the following pain points when trying to write unit tests:
export async function waitForEvent<T>(
event: vscode.Event<T>
): Promise<vscode.Disposable> {
return new Promise((resolve) => {
const disposable = event((_) => {
resolve(disposable);
});
});
}
export async function executeAndWaitForEvent<T, ET>(
func: () => Thenable<T>,
event: vscode.Event<ET>
): Promise<T> {
const [res, disposable] = await Promise.all([func(), waitForEvent(event)]);
disposable.dispose();
return res;
} They are unfortunately only doing the "right thing" if none of your listeners is async. Once one of them is asynchronous, it will not wait for it to complete (I guess this might be actually a limitation in vscode).
|
Since I was mentioned, just dropping one more rather annoying case I had to test recently: quickpicks
createQuickPick = stub(vscode.window, 'createQuickPick').callsFake(() => {
const picker = original();
acceptQuickPick = new EventEmitter<void>();
stub(picker, 'onDidAccept').callsFake(acceptQuickPick.event);
return picker;
}); Then in my tests, run a command and wait for the picker to be created: vscode.commands.executeCommand(Contributions.StartProfileCommand, session.id);
const typePicker = await eventuallyOk(() => { // test helper that retries unti it doesn't throw
expect(createQuickPick.callCount).to.equal(1);
const picker: vscode.QuickPick<vscode.QuickPickItem> = createQuickPick.getCall(0).returnValue;
expect(picker.items).to.not.be.empty; // needed since here we set items asynchronously
return picker;
}, 2000);
typePicker.selectedItems = typePicker.items.filter(i => /CPU/i.test(i.label));
acceptQuickPick.fire(); |
I've been using Sinon stubs to test QuickPicks roughly like this: this.fixture.vscodeWindow.showQuickPick.onCall(0).resolves(userInput);
await myProvider.cmdFunc(element).should.be.fulfilled;
this.fixture.sandbox.assert.calledOnce(
this.fixture.vscodeWindow.showQuickPick
);
this.fixture.sandbox.assert.calledWithMatch(
this.fixture.vscodeWindow.showQuickPick.firstCall,
[option1, option2, option3]
); That will however probably break when you try to use it via commands. Given the amount of setup code this requires, I am currently migrating most of these tests to vscode-extension-tester, which is perfect for testing UI elements (albeit a bit flaky from time to time). |
Yea |
Thanks @Almenon for raising the issue. Agree there is too much friction right now. I've only created simple stuff so far, but ran into the exact issue you described where nothing in my extensions (or libraries) was unit testable because everything depended directly or indirectly on
With this strategy only the entry point (index.js in my library, but could be extension.js for an extension) imports |
What might help (at least it would help me) would be a "test/mock mode" for vscode or some other library that you'd inject via dependency injection. This would allow you to setup expected user input and give you convenience functions to check that certain things happened. it("inputBox rejects a number", async () => {
const someObj = new Something();
vscodeTest.setUserInput(InputBox, "123");
await someObj.askForName();
vscodeTest.assertError(InputBox, "Invalid name: it contains only numbers");
}); The API that I've written down is of course terrible, but something along those lines would be certainly helpful although it overlaps a bit with vscode-extension-tester. |
Today in the Python extension we mock most of the VS Code API. We (Python Extension) find that to be very simple, however it could be a lot simpler if we were using SinonJs. As for full blow UI tests, the Python extension did go down this route. Basically we used the VS code Smoke Tests infrastructure and built on top of that to run full blown UI tests. However we stopped using that simply because we didn't see much value in it (i.e. it didn't get used much). If anyone is interested in the UI tests we had, it can be found in this PR (where it was remove). |
In case others still want examples for testing: I found the mocks here useful. |
I agree with everyone above, we have a reasonably mature set of tests but sometimes the amount of sinon and rewire in there scares me. You get to thinking are you testing the mocking frameworks more than the actual VSCode API. I have to say for a well used application development framework the documentation and testing components are woefully inadequate and the examples that are official are so naive to be embarassing TBH. Even when I look at repos like the docker extension the level of testing they have done is so cursory it's quite shocking. I'd love to see or have somewhere where the community could actually make this better as we all seem to have hacked around it in some ways (The RedHat stuff is almost the same our fake context/memento objects) and others have done the same with quickpicks. Why not make some kind of repo/package that has all the best practices inside it? |
We closed this issue because we don't plan to address it in the foreseeable future. If you disagree and feel that this issue is crucial: we are happy to listen and to reconsider. If you wonder what we are up to, please see our roadmap and issue reporting guidelines. Thanks for your understanding, and happy coding! |
Refs: #93604
Maybe we could provide a mock library for certain vs code types, and guidance (docs, examples, blogs, etc) on how it can be done effectively.
/cc @jrieken
The text was updated successfully, but these errors were encountered: