if
in a test. It is a sign that you're either not stubbing
correctly or you're testing multiple things in one test.expect
QUnit statement. You should always write your test in
a way that every assertion you set up will be hit 100%.beforeEach/afterEach
, but don't overuse this
feature. If you have a longer module, you might not see what the test does
because you don't know its setup.beforeEach/afterEach
. It is
very rare that all tests in a module have the same constructor. Furthermore,
using a global constructor object is dangerous.If you stick to these rules, you will find it much easier to refactor/maintain your tests. Keeping the tests atomic will make debugging much easier, because you will hit your breakpoints for the code being tested only. If you write QUnits without keeping to these rules, you may well not notice anything bad to begin with, but you will eventually end up in the middle of a maintenance nightmare!
Internally, we use three templates for testing. The one shown below is the general control template.
Use the following pattern to structure your tests. If everyone sticks to this same pattern, you will be able to read your colleagues' tests very quickly:
QUnit.test("Should do Something", function (assert) { // Arrange // System under Test var oMyControl = new nameSpace.myControl({ }); // Act // Assert // Cleanup oMyControl.destroy(); });
In Arrange
, you should set up the dependencies and options you need for your System under
Test
.
Examples:
In System under Test
, you should create your control and you should also render it if you want to test the
rendering.
Ideally, this part is only one single line of code executing the function you want to test.
This part may contain multiple statements of QUnit assertions, but ideally not too many in total.
Make sure that you also test negative paths, not only the expected ones.
Here you should destroy all the controls/models you created.
If you don't use Sinon sandboxes, revert all the spies/stubs/mocks.
In the rendering tests part, you have to place your control in the DOM. The best place to put
it is the qunit-fixture
div, since its content gets deleted after
every test.
Make sure you destroy your control, since SAPUI5 will keep a reference to it and may also rerender it.
It's crucial that you call sap.ui.getCore().applyChanges()
after each time
you have caused a rerendering.
The call to this function synchronizes the changes of your control with the DOM. If you do not make this call, the DOM will not be updated.
You can use the following template to make sure that you don't forget to destroy your control:
QUnit.test("Should do Something", function(assert) { // Arrange var oContructor = { }; // System under Test var oMyControl = new nameSpace.myControl(oContructor); oMyControl.placeAt("qunit-fixture"); sap.ui.getCore().applyChanges(); // Act // Assert // Cleanup oMyControl.destroy(); });
If you are using sinon.qunit
, it will automatically use fake timers by
itself. Fake timers will prevent any setTimeout/setIntervall
function from being executed, unless you call
this.clock.tick(milliseconds)
in your test. This means that
a Mock Server with auto-respond will not respond and OPA will not be able to
wait for controls.
In addition, control events might be fired inside of a setTimeout(, 0)
, so
the event might not be triggered at all.
If you want to test SAPUI5 events, you can use spies to test how often they are called. If you try to test the parameters, however, you cannot do this with spies as SAPUI5 uses an eventPool that reuses the same object again. This means that after an event is set, all of the parameters will be deleted, Sinon will keep a reference to the object without properties.
The effect of this is that you cannot assert on them anymore. The workaround is to use a stub with a custom implementation that saves a copy of the parameters to your test function scope.
An example of this is shown in the cookbook below (events).
The most likely reason for this is that sap.ui.getCore().applyChanges()
was not called. SAPUI5 does not render synchronously, but calling this function will render
immediately.