deno_std icon indicating copy to clipboard operation
deno_std copied to clipboard

bdd testing: easy testing of arrays

Open vixalien opened this issue 1 year ago • 2 comments

Is your feature request related to a problem? Please describe.

Yes. currently, it's not very easy to test arrays of data. For example, I'm writing a library that parses (rather complex) musical data into reusable JSON structures.

Take this example:

const data = get_data() as Data;

interface Data {
  title: string;
  categories: {
    title: string;
    results: (Playlist | Song | Album | Artist | ...)[];
  };
}

this is how I'm testing this currently

describe("data", () => {
  let data: Data;

  beforeAll(async () => {
    data = await get_data();
  })

  it("title", () => {
    assertEquals(typeof title, "string");
  });

  describe("categories", () => {
    it("present", () => {
      assert(data.categories.length > 0);
    });

    describe("category", () => {
      let category: any;

      beforeAll(() => {
        category = data.categories[0];
      });

      it("name", () => {
        assert(category.name.length > 0);
      });
  
      it("params", () => {
        assert(category.params.length > 0);
      });

      // I'm not even testing `category.results`, it's too complex
    })
  })
});

Describe the solution you'd like

It would be nice if for example the describe function could take in arrays I'm not sure (or an iterator).

describe("category", () => {
  let category: any;
  let id = 0;

  // Notice this is a generator function
  // this would be run each time unless it doesnt yield something
  beforeAll(* () => {
    while (true) {
      if (id >= data.categories.length) {
        break;
      }
      yield category = data.categories[id++];
    }
  });

  it("name", () => {
    assert(category.name.length > 0);
  });

  it("params", () => {
    assert(category.params.length > 0);
  });
})

or maybe something attached to describe

describe(
  "category",
  forEach = (id: number) => {
    this.category = data.categories[id];
    return id < data.categories.length;
  },
  fn() {
    it("name", function (this: { category: any }) => {
      assert(this.category.name.length > 0);
    });

    it("params", function (this: { category: any }) {
      assert(this.category.params.length > 0);
    });
  },
);

Describe alternatives you've considered

I've tried many alternatives, but no one really worked. right now I'm just using asserts (hence throwing away the benefits of bdd

data.categories.forEach((category) => {
  assertEqual(typeof category, "string"); 
  // etc...
)

vixalien avatar Mar 19 '23 14:03 vixalien

You can call it or describe in a for loop or a forEach block. Here is an example based on code samples you provided. In it, it will register a test group for each category with names in the format category[0], but you could construct the names to be whatever you want, even using variables from the category object. Then for each category, it would register 2 test steps, one for name and one for params.

data.categories.forEach((category, index) => {
  describe(`category[${index}]`, () => {
    it("name", () => {
      assert(category.name.length > 0);
    });

    it("params", () => {
      assert(category.params.length > 0);
    });
  });
});

If you have tests you only want to run for specific types of category objects, you could use if blocks to conditionally register different types of tests. And it doesn't all have to be in a single for loop or forEach, you could create functions for registering sets of tests then conditionally call them.

The describe/it function's are pretty flexible. I would personally prefer not adding something like the 2 examples provided. I believe it would make it more confusing to read and write test cases. In your first example, one might miss that there is a generator in their beforeAll when reading the test cases. For both examples, I'd have another question, which is what would the names for each of those cases generated? With the example I wrote, I believe it's much more clear how the test cases are being registered and what the names will be.

KyleJune avatar May 04 '23 02:05 KyleJune

I can't do data.categories.forEach because data itself is loaded in a beforeAll.

I haven't found a way to load data directly so this can't work.

vixalien avatar May 04 '23 08:05 vixalien