Programmatically close App Designer app after running it for a unit test

I need to run mlapp myApp for a unit test, but after the test I want the app window to be closed. The only way I know to do this is to search for the app UI Figure in all graphics objects, but I feel there should be a more elegant way.
In my example there is a function appStartup that is called by myApp when the app is started, and appStartup should throw an error if the app can't be run. My code looks like:
function testAppNotRunnable(testCase)
% code here to make sure the app can't run
verifyError(testCase, @() myApp, "appStartup:cantRunApp") % appStartup errors when called by myApp
% close open app windows
h = findall(groot, 'Tag', 'my app tag');
close(h)
This works, but seems inelegant.

 Accepted Answer

Original answer removed due to error. See discussion below.
Summary of approaches (thanks to Steven Lord)
  • use a addTeardown
  • use a try/catch block to catch errors in the startup function and close the app when there is an error
  • for public functions, call the function directly from the app object within verifyError and then close the app
  • If possible (outside of App Designer) reorganize code to move validation before figure creation

7 Comments

Pass the app handle to which function? The app is created by verifyError. If I try to define a handle to the app outside of verifyError (with app = myApp;) , then the error I'm trying to verify will happen at that time, and won't be caught by verifyError.
Oh right... I've updated my answer.
verifyError returns the app handle which can be used to close the app.
"verifyError returns the app handle which can be used to close the app."
No, it doesn't, unless the verifyError call fails to verify an error/exception. From the verifyError documentation page: "If the function handle throws an exception, all outputs are displayed as <missing>."
testcase = matlab.unittest.TestCase.forInteractiveUse;
output = verifyError(testcase, @() plus(1:2, 1:3), ...
"MATLAB:sizeDimensionsMustMatch")
Verification passed.
output = missing
<missing>
Compare to a case where the function being tested does not throw an exception (and so the verification fails.)
output = verifyError(testcase, @() plus(1:2, 1:2), ...
"MATLAB:sizeDimensionsMustMatch")
Verification failed. --------------------- Framework Diagnostic: --------------------- verifyError failed. --> The function did not throw any exception. Expected Exception: 'MATLAB:sizeDimensionsMustMatch' Evaluated Function: function_handle with value: @()plus(1:2,1:2) ------------------ Stack Information: ------------------ In /tmp/Editor_xdryi/LiveEditorEvaluationHelperEeditorId.m (LiveEditorEvaluationHelperEeditorId) at 4 In /MATLAB/toolbox/matlab/connector2/interpreter/+connector/+internal/fevalMatlab.p (fevalMatlab) at 0 In /MATLAB/toolbox/matlab/connector2/interpreter/+connector/+internal/fevalJSON.p (fevalJSON) at 0
output = 1x2
2 4
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
In this case one approach you could use would be to set up a teardown using addTeardown. Record the list of figures that existed prior to the test method running as the first line of the test. Add a teardown function that queries what figures exist when the teardown function executes and closes all the figures that are in the later list but not in the former (using setdiff.)
But I'd take a closer look at the app. Why does the app create the figure then error out? Can you reorganize the initialization code to error out before it creates the figure, perhaps pulling numeric data validation earlier in the initialization function? If the app constructor throws an error before the figure gets created, you only need to worry about closing the app figure if the test fails (in which case the output argument from verifyError can help.)
Looks like verifyError is not actually returning a handle to the app. With close(appHandle.UIFIgure) I get
Error ID:
---------
'MATLAB:noSuchMethodOrField'
--------------
Error Details:
--------------
Unrecognized method, property, or field 'UIFigure' for class 'missing'.
@Steven Lord I fully agree my software could be much better designed. The appStartup function I'm testing was created by moving code out of the mlapp file, so it's already better than it was. I have two challenges here: 1) I'm building from someone else's existing code, and 2) nobody on my team (including me) has experience in professional software development. I'm not sure which is worse.
:-)
I will look at your addTeardown suggestion, but I will also look into doing the startup routine before opening the app. That might not be too hard to implement.
Another possibility could be to wrap the startup code in your appStartup function in a try / catch block. If an error occurs in the try part of the block, close the figure (if it exists) before calling rethrow on the error.
try
x = (1:2) + (1:3);
catch ME
disp("Here is where I'd close the figure.")
rethrow(ME)
end
Here is where I'd close the figure.
Arrays have incompatible sizes for this operation.
Thanks for pointing out my mistake @Steven Lord.
If the app subclasses from matlab.apps.AppBase I'm not aware of a way to run one of its methods (e.g. the startup function) before opening/creating the app.
However, once the app object exists, you can independently call any of its public functions. For example, if appStartup is a public function in myApp and the expected error it throws is independent of the initialization at startup, you could verify the error using,
app = myApp;
verifyError(testCase, @() appStartup(app), "appStartup:cantRunApp")
close(app.UIFigure)
But I like Steven's addTeardown idea best and wish I had thought of it.

Sign in to comment.

More Answers (0)

Categories

Products

Release

R2023b

Asked:

on 16 May 2024

Edited:

on 16 May 2024

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!