Jasmine Autograde Unit Testing

Nick Updated by Nick

What is Jasmine?

Jasmine is a framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It runs in browsers and in Node.js. And it has a clean, obvious syntax so that you can easily write tests. Jasmine specs are just JavaScript. Jasmine doesn't change the way your code loads or runs.

JavaScript Development

Starter Pack: Jasmine HTML/CSS/JS Autograding

Stack: Jasmine HTML/CSS/JS Autograding

Package Requirements

{
"dependencies": {
"jsdom": "latest",
"lodash": "latest",
"node-fetch": "^2.6.1",
"jasmine": "latest"
}
}

The directory structure follows this pattern

Understanding the Codio Autograder Flow

File Structure and Execution Order

The autograder process in Codio follows a specific execution path:

  1. First, the .sh file (c1-m1-graded-project.sh) in .guides/secure is called by Codio's autograding system
  2. This shell script then triggers the JavaScript test file (m1-project.spec.js) located in .guides/secure/spec

This two-step process allows Codio to:

  • Execute the Jasmine test suite with proper configurations
  • Handle test results and reporting

The shell script acts as a bridge between Codio's autograding system and your custom Jasmine tests, with the tests being organized in the spec subdirectory.

Detailed Test Suite Breakdown

Reporter Setup
beforeAll(async () => {
try {
const response = await fetch('https://ecornell.s3.us-east-1.amazonaws.com/Codio/Courses/CIS540s/Reporter/CIS540sReporter.js?v=1.0.0');
const Reporter = eval(await response.text());
jasmine.getEnv().clearReporters();
jasmine.getEnv().addReporter(new Reporter());
} catch (error) {
console.error('Failed to load reporter: Please contact your course facilitator. \n Error: ', error);
}
});

This section initializes the custom reporter for test results. It:

  1. Fetches a custom reporter script.
    1. NOTE: This starter pack uses a reporter script specific to CIS540s linked from AWS. You will want to create and link a NEW reporter for the development of new courses in case changes are needed.
  2. Clears existing reporters
  3. Adds the custom reporter to Jasmine's environment
  4. In the event the custom reporter is unable to be fetched and returned, the catch block will report an error message to contact the facilitator who should then create a support ticket.
JSDOM Setup

let document;

beforeAll(() => {
const htmlContents = fs.readFileSync(path.resolve("./../../assignments/index.html"));
const jsdom = new JSDOM(htmlContents, {
resources: "usable",
runScripts: "dangerously"
});
document = jsdom.window.document;
});

The JSDOM setup:

  1. Reads the HTML file
  2. Creates a virtual DOM
  3. Enables resource loading and script execution
  4. Makes the document object available for testing

Adding New Tests

To add new tests to the suite, you'll need to understand the structure of Jasmine tests. The basic pattern is:

describe("Test Group Name", function () {
it("should do something specific", function () {
// Test code here
expect(something).toBe(someValue);
});
});

New tests can be added within existing describe blocks or in new ones. Always ensure your tests are:

  • Descriptive in their naming
  • Focused on a single aspect
  • Clear in their expectations

Understanding the 'describe' Function

The describe function is a fundamental building block in Jasmine that creates a suite of tests. Think of it as a container for related tests:

describe("HTML, CSS, and JavaScript Validation", function () {
// Setup code that runs before all tests
beforeAll(() => {
// Initialize JSDOM, load files, etc.
});

// Individual test groups
describe("HTML Tests", () => {
// HTML specific tests
});

describe("CSS Tests", () => {
// CSS specific tests
});

describe("JavaScript Tests", () => {
// JavaScript specific tests
});
});

Key features of describe:

  • Groups related tests together
  • Can be nested for better organization
  • Can share setup code via beforeAll and beforeEach
  • Provides context for test failures

The 'it' Function Explained

The it function defines individual test cases. Each it block should test one specific thing:

it("CSS file should exist and be valid", function() {
// Test code here
});

Important aspects of it:

  • First parameter is a description of what's being tested
  • Second parameter is a function containing the test code
  • Can be async using async/await or by accepting a done callback
  • Should contain one or more expectations using expect()

HTML Validation In-Depth

The HTML validation process involves several layers:

  1. Basic Structure Validation
  2. Tag Matching System
  3. Semantic Structure Tests

CSS Validation Explained

CSS validation occurs in multiple stages:

  1. File Existence and Basic Structure
    1. it("CSS file should exist and be valid", function() {
      const cssPath = path.resolve("./../../assignments/styles/site.css");
      const cssContent = fs.readFileSync(cssPath, "utf8");

      // Normalize CSS content
      const normalizedCss = cssContent
      .replace(/\/\*[\s\S]*?\*\//g, '')
      .replace(/[\n\r]/g, '')
      .replace(/\s+/g, ' ')
      .trim();
      });
  2. Rule Structure Validation
    1. const cssRules = normalizedCss.split('}').filter(rule => rule.trim());
      cssRules.forEach(rule => {
      const parts = rule.split('{');
      // Validate selector and declarations
      });
  3. Property Validation (if needed)
    1. // Example of validating specific properties const validateProperties = (declarations) => { declarations.split(';').forEach(declaration => { const [property, value] = declaration.split(':').map(s => s.trim()); // Validate property and value }); };

JavaScript Validation Detailed

JavaScript validation includes several checks:

  1. Basic File Validation
    1. it("JavaScript file should exist and be valid", function() {
      const jsPath = path.resolve("./../../assignments/js/main.js");
      const jsContent = fs.readFileSync(jsPath, "utf8");

      // Clean up JS content
      const normalizedJs = jsContent
      .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
      .replace(/\/\/.*/g, '') // Remove line comments
      .replace(/[\n\r]/g, '') // Remove newlines
      .trim();
      });
  2. Syntax Validation
    1. try {
      new Function(normalizedJs); // Basic syntax check
      } catch (e) {
      if (e instanceof SyntaxError) {
      fail(`JavaScript syntax error: ${e.message}`);
      }
      }
  3. Script Loading Validation
    1. it("JavaScript should be properly linked in HTML", function() {
      const jsScript = document.querySelector('script[src="js/main.js"]');
      expect(jsScript).toBeTruthy();
      });

Additional Resources and Final Notes

Further Reading

Final Notes

  1. Test Maintenance
    • Regularly update dependencies
    • Review test coverage
    • Keep test files organized and well-documented
  2. Common Gotchas
    • Always use relative paths in your tests
    • Remember JSDOM has limitations compared to real browsers
    • Handle asynchronous operations properly
  3. Best Practices
    • Write tests that are independent of each other
    • Keep test descriptions clear and specific
    • Use beforeAll() and beforeEach() appropriately
    • Consider test performance and execution time

How did we do?

Setting Up the Class Fork

Setting Up the Class Fork (LTI 1.3)

Contact