I am developing a JavaScript library (https://github.com/yvesgurcan/web-midi-player) to enable MIDI playback in a web application. The library relies on the Web Audio API to create a way to play these MIDI files (https://github.com/yvesgurcan/web-midi-player/blob/test/src/MidiPlayer.js#L50). However, I am having trouble creating meaningful unit tests with Jest (https://github.com/yvesgurcan/web-midi-player/blob/test/tests/midiPlayer.js) because these tests don't have access to the window object and more particularly to window.AudioContext. As a consequence, running my application code which relies on AudioContext throws errors related to the fact that this object does not exist and I can't actually test very much things in the library.
I've tried the following packages to solve my problem: jsdom, jsdom-global, and also web-audio-test-api but none of these seem to inject AudioContext in the environment.
I am thinking that the solution here would be to stub/mock AudioContext but that does not sound like a good solution for solid unit tests.
What do you folks suggest to test the Web Audio API? Is stubbing the only viable solution here?
I think it depends a bit on what you want to test. Since you're using Jest I imagine you're just interested in testing the correctness of your own code. In that case I would recommend to fully mock the Web Audio API. It's not part of your responsibility and you can assume it works the way it should. The only thing you have to test is if your code is making the expected calls.
Mocking globally available variables like the AudioContext constructor is always a bit tricky but you could allow an AudioContext to be passed into your MidiPlayer class as an optional argument. It would make testing a little easier and it would also allow users of your library to bring their own AudioContext.
I think of something like this:
class MidiPlayer {
constructor({
// ... the other options ...
context = new AudioContext()
}) {
// ...
}
}
Inside a test you could then simply instantiate the MidiPlayer with a fake AudioContext.
const fakeAudioContext = {
currentTime: 3,
// ... and all the other things your code uses ...
};
const midiPlayer = new MidiPlayer({ context: fakeAudioContext });
I recently answered a similar question related to Tone.js which might be helpful. The basic idea is the same.
In case you want to test if your library works nicely with the Web Audio API in a browser I would recommend to use a test runner like Karma. It executes the tests in a real browser and therefore can use all the browser APIs.
Related
I am trying to build a test suite for my react application. A component in the application uses Audio:
const audio = new Audio('beep.wav');
audio.play();
which works in the browser but according to this, is not on node.js. So when I use enzyme to try to render the component it gives an error:
ReferenceError: Audio is not defined
I am new to testing, but is there a way to get around this? Can I somehow mock an object to take Audio's place? I am new to font-end/javascript unit testing so I am still trying to learn the tricks.
I see two options.
Pass Audio object into your testing component as property + during test, you will create Sinon's stub or spy and pass it to the component as fake dependency.
Since version 9.3.0, jsDOM seem to support Audio constructor. So you can simulate browser environment via jsDOM in your test.
I am currently working on a calculator that will run as a packaged (desktop) chrome app. I am using the math.js library to parse math input. This is my old code:
evaluate.js:
var parser = math.parser();
function evaluate(input){
$("#output").text(parser.eval(input));
}
However, if the input is something unreasonable like 6234523412368492857483928!, the app just freezes, because it is trying to evaluate the input. I know that math.js is still in beta so eventually there might be a fix (overflow errors), but I couldn't find any other library that parses raw input the way math.js does.
To fix this, I am trying to fix this using web workers to run it asynchronously. Here is the code that I have right now:
main.js
var evaluator = new Worker('evaluate.js');
evaluator.addEventListener('message', function(e){
$("#output").text(e.data);
}, false);
function evaluate(input){
evaluator.postMessage(input);
}
evaluate.js
var parser = math.parser();
function mathEval(input){
return parser.eval(input);
}
self.addEventListener('message', function(e){
self.postMessage(mathEval(e.data));
});
However, this doesn't work when I run it. Also, I noticed that when I use web workers, it throws the error Uncaught ReferenceError: math is not defined - evaluate.js:1, but it didn't throw this error with the old code.
Questions: Why doesn't this code work to evaluate the input? Is it possible to use multiple workers to speed it up? If I wanted to implement some sort of overflow error for when the worker takes more than 2 seconds, what would be the best way to go about doing it? Finally, is there a better way to do this?
Web Workers are run in totally separate context. They don't have access to the objects from parent web page. If you want to use math.js you have to import it into the worker using importScript.
I recommend to read Using Web Workers guide, part "Importing Scripts And Libraries" which describes how to do it, and how it works in detail.
So, there is a WebRTC inside Firefox and there is a convenient class for making RTC communication possible called RTCPeerConnection which can be instantiated and used from the JavaScript app. You can find some decent example of it on [1].
And here am I with my custom transport (if you're interested - [2]) would like to use it for RTC communication. Briefly, I need to "substitute" the transport layer of WebRTC engine by my custom transport while providing the same RTCPeerConnection-like JavaScript interface for the user. And preferably, it should not look like custom build of Firefox (no patches).
So I've come up with the idea of extension, which will be written in C++ (since it need to be linked with WebRTC library and my custom transport library) and somehow will expose its interface to Javascript. And I've found XPCOM which, as I thought, can provide me this.
So I've started to fight with out-dated and sparsed info on this topic and after 3 days of struggling finally ended up with builded add-on. Unfortunately, I can't access it from Javascript, because of Javascript's "Components.classes is undefined" error. And it seems that there is no way to access it at all. Or I'm wrong on that?
Here is Javascript:
function check()
{
console.debug("checking...");
const {Cc,Ci,Cu} = require("chrome");
var rtc = Components.classes["#named-data.net/ndnrtc;1"].createInstance();
rtc = rtc.QueryInterface(Ci.ndINrtc);
console.debug("rtc: "+rtc);
}
My component is visible with XPCOM Viewer addon and the code above I can execute in the console while empty page is open in Firefox.
Given all that, I would like to ask Firefox experts regarding possible approaches which I can take in order to implement my idea.
Thank you in advance
1 https://apprtc.appspot.com/
2 http://named-data.net
Finally, I've figured out one possible solution for that and describe it in my post
I am trying to unit test a JavaScript web tracking function because we intend to expose it as a library and want to be sure its interface remains stable. JavaScript web tracking works similarly to image preloading like this:
if (document.images) {
var image = new Image();
image.onload = function () {
...
};
image.src = pixelUrl;
}
When in a unit test (with js-testdriver) I am now trying to validate that an image has been created with the right pixelUrl I thought I had to do something like this:
call_the_reporting_function();
assertNotUndefined(document.images[0]);
assertNotUndefined(document.images[0].src);
My problem is now that the first assertion already fails. It seems that the new image is not added to document.images immediately.
Does anybody has an idea how to do proper unit testing on such things in JavaScript?
Thanks
Stefan
One possible solution, which is the way we just took, is to circumvent the problem by extracting the part above doing the html manipulation and mocking it.
While this solution is fine, it certainly has at least two downsides:
- there is no point using a browser-based unit test framework (like jsTestDriver, which is, what we are using) if you do not use the browser-specific parts
- the code is untested and you have to ensure that it works either manually or using runtime-expensive Selenium tests (where you can configure a proxy before the HttpClient and simply catch all web tracking requests issued by the browser. Assertions are done on the caught requests)
I'm looking for framework which provides unit tests for JS. Later, I'll have to integrate it with CruiseControl.NET. I have a lot of asp websites using JS and I need to automate testing for them. For example I used local JSUnit (may be other framework), which was quite easy for me, but I have no idea how to integrate it with CC.NET. Maybe you can show me how to start with ?
NOTE: it must be CC.NET, framework for unit test not specified
I realize this is a rather old question, but I have had similar interests so I figure I would post an answer regardless. I am assuming you are writting OO style JavaScript and wanting to test those objects.
A common solution recommended by people is to run JsTestDriver and this is definitely a great tool; that being said, it requires the JVM to be installed on your machine as well as the build server. In addition to requiring the JVM, JsTestDriver is still dependant on running one or more 'Slave' browsers. Personally, I believe that should not be a concern when unit testing JavaScript objects. If the focus is UI testing, then using a tool such as WatiN and is a different type of test in my opinion. The browser should be mock out like you would mock out any other view using JSMock or JsMockito (which I like best).
To that end, I have used Windows Script Host to run my JavaScript unit tests for years. This has several advantages:
No Browser Required (to me that is a good thing).
CScript is already on every Windows machine since XP; so nothing to install.
Runs in background process and does not require an interactive UI when running tests.
Since running inside MSTest, NUnit or xUnit etc, integrating with CC.NET, Hudson, or TeamCity is as easy as running your unit tests as a part of the build.
Full debug support simply by running your unit test inside the debugger.
There are a couple of disadvantages of course:
The machine running the unit tests has to have the ability to spawn off new processes (CSCript) to actually run the tests (has yet to be an issue).
Slower that running regular unit tests (A little over 1,000/minute on an older dual core if run individually - i.e., not bundled together in a single CSCript process).
Using JSTest.NET (approach I have used for years but just posted online), you end up with a test fixture that may look similar to the following:
using JSTest;
using JSTest.ScriptLibraries;
using Xunit;
namespace JSTest.Sample
{
public class EventDispatcherTests
{
private readonly TestScript _testScript = new TestScript();
public EventDispatcherTests()
{
//Append File(s) Under Test.
_testScript.AppendFile(#"..\..\..\Web\Scripts\eventDispatcher.js");
//Append Mock/Assertion libraries.
_testScript.AppendBlock(new JsHamcrestLibrary());
_testScript.AppendBlock(new JsMockitoLibrary());
//Append Common "Setup" Scripts.
_testScript.AppendBlock(#"var dispatcher = new EventDispatcher();
var mockHandler = JsMockito.mockFunction();
var mockPredicate = JsMockito.mockFunction();
var fakeEvent = { eventType: 'CommentAdded', data: 'Sample Comment' };
");
}
[Fact]
public void ProcessEventInvokesAttachedHandler()
{
...
}
[Fact]
public void ProcessEventGracefullyHandlesPredicateException()
{
_testScript.RunTest(#"var mockPredicateAlternate = JsMockito.mockFunction();
JsMockito.when(mockPredicateAlternate)(fakeEvent).thenThrow('MyException');
dispatcher.attachListener(mockHandler, mockPredicateAlternate);
dispatcher.processEvent(fakeEvent);
JsMockito.verify(mockPredicateAlternate)(fakeEvent);
JsMockito.verifyZeroInteractions(mockHandler);
");
}
}
}
This approach works well for me, and is very simple to setup. Basically, it is like writing regular unit tests, but with JavaScript.