Create Advanced Custom Fixture

This example shows how to create a custom fixture that sets an environment variable. Prior to testing, this fixture will save the current UserName variable.

Create UserNameEnvironmentVariableFixture Class Definition

In a file in your working folder, create a new class, UserNameEnvironmentVariableFixture that inherits from the matlab.unittest.fixtures.Fixture class. Since you want to pass the fixture a user name, create a UserName property to pass the data between methods.

classdef UserNameEnvironmentVariableFixture < ...
        matlab.unittest.fixtures.Fixture
    
    properties (SetAccess=private)
        UserName
    end

Define Fixture Constructor

In the methods block of the UserNameEnvironmentVariableFixture.m file, create a constructor method that validates the input and defines the SetupDescription. Have the constructor accept a character vector and set the fixture’s UserName property.

    methods
        function fixture = UserNameEnvironmentVariableFixture(name)
            validateattributes(name, {'char'}, {'row'}, '','UserName')
            fixture.UserName = name;
            fixture.SetupDescription = sprintf( ...
                'Set the UserName environment variable to "%s".',...
                fixture.UserName);
        end

Implement setup Method

Subclasses of the Fixture class must implement the setup method. Use this method to save the original UserName variable. This method also defines the TeardownDescription and registers the teardown task of setting the UserName to the original state after testing.

Define the setup method within the methods block of the UserNameEnvironmentVariableFixture.m file.

        function setup(fixture)
            originalUserName = getenv('UserName');
            fixture.assertNotEmpty(originalUserName, ...
                'An existing UserName environment variable must be defined.')
            fixture.addTeardown(@setenv, 'UserName', originalUserName)
            fixture.TeardownDescription = sprintf(...
                'Restored the UserName environment variable to "%s".',...
                originalUserName);
            setenv('UserName', fixture.UserName)
        end
    end

Implement isCompatible Method

Classes that derive from Fixture must implement the isCompatible method if the constructor is configurable. Since you can configure the UserName property through the constructor, UserNameEnvironmentVariableFixture must implement isCompatible.

The isCompatible method is called with two instances of the same class. In this case, it is called with two instances of UserNameEnvironmentVariableFixture. The testing framework considers the two instances compatible if their UserName properties are equal.

In a new methods block within UserNameEnvironmentVariableFixture.m, define an isCompatible method which returns logical 1 (true) or logical 0 (false).

    methods (Access=protected)
        function bool = isCompatible(fixture, other)
            bool = strcmp(fixture.UserName, other.UserName);
        end
    end

Fixture Class Definition Summary

Below are the complete contents of UserNameEnvironmentVariableFixture.m.

classdef UserNameEnvironmentVariableFixture < ...
        matlab.unittest.fixtures.Fixture
    
    properties (SetAccess=private)
        UserName
    end
    
    methods
        function fixture = UserNameEnvironmentVariableFixture(name)
            validateattributes(name, {'char'}, {'row'}, '','UserName')
            fixture.UserName = name;
            fixture.SetupDescription = sprintf( ...
                'Set the UserName environment variable to "%s".',...
                fixture.UserName);
        end
        
        function setup(fixture)
            originalUserName = getenv('UserName');
            fixture.assertNotEmpty(originalUserName, ...
                'An existing UserName environment variable must be defined.')
            fixture.addTeardown(@setenv, 'UserName', originalUserName)
            fixture.TeardownDescription = sprintf(...
                'Restored the UserName environment variable to "%s".',...
                originalUserName);
            setenv('UserName', fixture.UserName)
        end
    end
    
    methods (Access=protected)
        function bool = isCompatible(fixture, other)
            bool = strcmp(fixture.UserName, other.UserName);
        end
    end
end

Apply Custom Fixture to Single Test Class

In a file in your working folder, create the following test class, ExampleTest.m.

classdef ExampleTest < matlab.unittest.TestCase
    methods(TestMethodSetup)
        function mySetup(testCase)
            testCase.applyFixture(...
                UserNameEnvironmentVariableFixture('David'));
        end
    end
    
    methods (Test)
        function t1(~)
            fprintf(1, 'Current UserName: "%s"', getenv('UserName'))
        end
    end
end

This test uses the UserNameEnvironmentVariableFixture for each test in the ExampleTest class.

At the command prompt, run the test.

run(ExampleTest);
Running ExampleTest
Current UserName: "David".
Done ExampleTest
__________

Apply Custom Fixture as Shared Fixture

In your working folder, create three test classes using a shared fixture. Using a shared fixture allows the UserNameEnvironmentVariableFixture to be shared across classes.

Create testA.m as follows.

classdef (SharedTestFixtures={...
        UserNameEnvironmentVariableFixture('David')}) ...
        testA < matlab.unittest.TestCase
    methods (Test)
        function t1(~)
            fprintf(1, 'Current UserName: "%s"', getenv('UserName'))
        end
    end
end

Create testB.m as follows.

classdef (SharedTestFixtures={...
        UserNameEnvironmentVariableFixture('Andy')}) ...
        testB < matlab.unittest.TestCase
    methods (Test)
        function t1(~)
            fprintf(1, 'Current UserName: "%s"', getenv('UserName'))
        end
    end
end

Create testC.m as follows.

classdef (SharedTestFixtures={...
        UserNameEnvironmentVariableFixture('Andy')}) ...
        testC < matlab.unittest.TestCase
    methods (Test)
        function t1(~)
            fprintf(1, 'Current UserName: "%s"', getenv('UserName'))
        end
    end
end

At the command prompt, run the tests.

runtests({'testA','testB','testC'});
Setting up UserNameEnvironmentVariableFixture
Done setting up UserNameEnvironmentVariableFixture: Set the UserName environment variable to "David".
__________

Running testA
Current UserName: "David".
Done testA
__________

Tearing down UserNameEnvironmentVariableFixture
Done tearing down UserNameEnvironmentVariableFixture: Restored the UserName environment variable to "Kim".
__________

Setting up UserNameEnvironmentVariableFixture
Done setting up UserNameEnvironmentVariableFixture: Set the UserName environment variable to "Andy".
__________

Running testB
Current UserName: "Andy".
Done testB
__________

Running testC
Current UserName: "Andy".
Done testC
__________

Tearing down UserNameEnvironmentVariableFixture
Done tearing down UserNameEnvironmentVariableFixture: Restored the UserName environment variable to "Kim".
__________

Recall that the fixtures are compatible if their UserName properties match. The tests in testA and testB use incompatible shared fixtures, since 'David' is not equal to 'Andy'. Therefore, the framework invokes the fixture teardown and setup methods between calls to testA and testB. However, the shared test fixture in testC is compatible with the fixture in testB, so the framework does not repeat fixture teardown and setup before testC.

Alternative Approach to Calling addTeardown in setup Method

An alternate approach to using the addTeardown method within the setup method is to implement a separate teardown method . Instead of the setup method described above, implement the following setup and teardown methods within UserNameEnvironmentVariableFixture.m.

 Alternate UserNameEnvironmentVariableFixture Class Definition

The setup method does not contain a call to addTeardown or a definition for TeardownDescription. These tasks are relegated to the teardown method. The alternative class definition contains an additional property, OriginalUser, which allows the information to be passed between methods.

See Also

Related Topics