Forum Discussion
Unit testing a custom visual: how do you test update() and formatting settings?
jubinsoni Hi, I'd recommend a layered testing strategy:
- Unit tests: Jest works well for testing pure functions like your toViewModel(), including edge cases such as missing values, nulls, empty arrays, and large datasets.
- Visual lifecycle testing: Microsoft's powerbi-visuals-utils-testutils package provides useful helpers such as VisualBuilderBase and DataViewBuilder. These allow you to simulate update() calls with mocked DataViews and validate the resulting DOM. Microsoft's documented setup uses Karma and Jasmine, although Jest with jsdom can also be considered depending on your dependencies.
- Realistic DataViews: I'd suggest creating reusable DataView factories covering empty datasets, null values, multiple categories, and different metadata configurations. This helps avoid duplicating complex mock structures across tests.
- Formatting settings: Definitely test these separately. Validate default values, parsing of metadata objects, and how formatting changes affect the visual. It's also worth checking that object and property names in the formatting model align with those defined in capabilities.json.
One important distinction: unit tests can validate the formatting model and its mapping, but they don't fully guarantee how the formatting pane behaves inside Power BI. A final integration check in Power BI Desktop or Service is still valuable.
Microsoft also provides useful documentation and examples here:
Overall, I'd prioritize fast unit tests for transformation logic, targeted tests for update() and formatting behavior, and a small set of integration tests for host-specific functionality.
BBF
// viewModel.ts
export interface ViewModelItem {
category: string;
value: number;
}
export function toViewModel(
categories: string[],
values: (number | null | undefined)[]
): ViewModelItem[] {
return categories.map((category, index) => ({
category,
value: values[index] ?? 0
}));
}
__________________________________________________________________________
// viewModel.spec.ts
import { toViewModel } from "./viewModel";
describe("toViewModel", () => {
test("pads missing values with zero", () => {
expect(toViewModel(["a", "b"], [1])).toEqual([
{ category: "a", value: 1 },
{ category: "b", value: 0 }
]);
});
test("handles null and undefined values", () => {
expect(
toViewModel(["a", "b", "c"], [10, null, undefined])
).toEqual([
{ category: "a", value: 10 },
{ category: "b", value: 0 },
{ category: "c", value: 0 }
]);
});
test("handles empty datasets", () => {
expect(toViewModel([], [])).toEqual([]);
});
test("preserves zero and negative values", () => {
expect(toViewModel(["a", "b"], [0, -5])).toEqual([
{ category: "a", value: 0 },
{ category: "b", value: -5 }
]);
});
test("handles large datasets", () => {
const categories = Array.from(
{ length: 1000 },
(_, i) => `Category ${i}`
);
const values = Array.from(
{ length: 1000 },
(_, i) => i
);
const result = toViewModel(categories, values);
expect(result).toHaveLength(1000);
expect(result[999]).toEqual({
category: "Category 999",
value: 999
});
});
});