Skip to content

Regular Expressions

Regular expressions are used to test text in Component Tests.

Regular expressions can be used to test any language but all regular expression tests are written in as JavaScript code snippets.

Example Regular Expression Test

// First
if (!Components.CodeEditor.codeContains('script.js', /(var|const|let)\s+variableName\s*=\s*3/)) {
  return {
    pass: false,
    errors: {
      friendly: 'Did you ... ?',
      component: 'PersistentCodeEditor'
    }
  };
}

// Return the success object by default if none of the above test(s)
return { pass: true };

Writing Flexible Regular Expressions

It's easy to be overly prescriptive when writing Regular Expression tests. Aim to pass code that is syntactically accurate and achieves the desired result, even if it is not exactly what you intend as solution code.

Allowing for anything

Sometimes it is helpful to allow arbitrary code at some point in a Regular Expression. Various Regular Expression snippets can be used to match any set of characters:

/[\s\S]*/
/[^]*/
/[\s.]*/

For example, to test that a my_farmer.feed_the_chickens() Python method is called in a code file with any argument (but at least one argument):

/my_farmer\.feed_the_chickens\(\s*\S+\s*\)/

In this case, the .feed_the_chickens() method will only match if it is called with at least one non-whitespace \S+ character with optional whitespace on either side \s* for added flexibility.

Allowing for Whitespace

Many languages are flexible about whitespace, you should be too! For example, the following are all valid ways of declaring and assigning the same value to a variable:

var myVariable = 1;
var myVariable=1;
var myVariable =1;
var myVariable= 1;
var myVariable =      1    ;
var       myVariable = 1;
var myVariable = 1
var
  myVariable
=1;

While they may not all be best practice, they are all valid code. The following Regular Expression can match all of the above examples:

/var\s+myVariable\s*=\s*1;?/

Proper use of the \s character class and the + and * quantifier help with this: \s* matches zero or more whitespace characters (useful for situations like surrounding the = in this example, where space is often present but not syntactically required). The \s+ matches one or more characters of whitespace (helpful when at least one space is necessary but more space is also valid, such as the space between the var keyword and the variable name).

Using Capture Groups for Matching

In some JavaScript code, we'd like to check for a matching pair of any type of quotes: single (''), double (""), or backticks (``). We can capture the first quote using a capture group ('|"|`), then refer to it with a numeric reference (\1):

console\s*.\s*log\s*\(\s*('|"|`)hello\s+world\.?\1\s*\)

The above regex will match any of (but not limited to) the following:

console.log('hello world')
console.log("hello world")
console.log(`hello world`)

Using Regex with Advanced Logic

The Component Test type supports any JavaScript logic, so we can make multiple levels of assertions.

For example, say we want to make assertions on the below code. We'd like to check that:

  1. The Configure method is defined and includes a call to app.UseHttpsRedirection()
  2. Within the method, before app.UseHttpsRedirection(), there is an if statement that checks if env.IsDevelopment() is true
  3. Within that if body, there is a call to app.UseDeveloperExceptionPage()
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  if (env.IsDevelopment())
  {
    app.UseDeveloperExceptionPage();
  }

  app.UseHttpsRedirection();
}

We can access the entire file contents using Components.CodeEditor.readFileContent() and check for regex matches using String.match(). We use capture groups and check for further regex matches within each one:

// Save code file as a string
var code = Components.CodeEditor.readFileContent('Startup.cs');

// Match a regex pattern and capture a value from learner code
var codeMatches = code.match(/Configure\s*\(.*\)\s*\{\s*([\s\S]*)app\.UseHttpsRedirection/);

if (!codeMatches) {
  return {
    pass: false,
    errors: {
      friendly: 'Make sure `Configure()` is correctly defined.',
      component: 'PersistentCodeEditor'
    }
  };
} else {
  var usesIf = codeMatches[1].match(/if\s*\(\s*env\.IsDevelopment\s*\(\s*\)\s*\)/);
  var usesMiddleware = codeMatches[1].match(/app\.UseDeveloperExceptionPage\s*\(\s*\)/);

  if (!usesIf) {
    return {
      pass: false,
        errors: {
        friendly: 'Create an `if` statement within `Configure()` that checks the value of `env.IsDevelopment()`.',
        component: 'PersistentCodeEditor'
        } 
    }
  }

  if (!usesMiddleware) {
    return {
      pass: false,
        errors: {
        friendly: 'Within the `if` statement, call `app.UseDeveloperExceptionPage()`.',
        component: 'PersistentCodeEditor'
        } 
    }
  }

  return { pass: true };
}