Skip to content

Commit

Permalink
fix: handle test method covering multiple classes (#122)
Browse files Browse the repository at this point in the history
@W-8674084@
  • Loading branch information
AnanyaJha authored and jag-j committed Jan 21, 2021
1 parent f00f66c commit abe8149
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 111 deletions.
34 changes: 16 additions & 18 deletions packages/apex-node/src/reporters/humanReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,31 +129,29 @@ export class HumanReporter {
private formatDetailedCov(testResult: TestResult): string {
const tb = new Table();
const testRowArray: Row[] = [];
testResult.tests.forEach(
(elem: {
fullName: string;
outcome: string;
perTestCoverage?: {
apexClassOrTriggerName: string;
percentage: string;
};
message: string | null;
runTime: number;
}) => {
testResult.tests.forEach((elem: ApexTestResultData) => {
if (elem.perClassCoverage) {
elem.perClassCoverage.forEach(perClassCov => {
testRowArray.push({
name: elem.fullName,
coveredClassName: perClassCov.apexClassOrTriggerName,
outcome: elem.outcome,
coveredClassPercentage: perClassCov.percentage,
msg: elem.message ?? '',
runtime: `${elem.runTime}`
});
});
} else {
testRowArray.push({
name: elem.fullName,
coveredClassName: elem.perTestCoverage
? elem.perTestCoverage.apexClassOrTriggerName
: '',
coveredClassName: '',
outcome: elem.outcome,
coveredClassPercentage: elem.perTestCoverage
? elem.perTestCoverage.percentage
: '',
coveredClassPercentage: '',
msg: elem.message ? elem.message : '',
runtime: `${elem.runTime}`
});
}
);
});

let detailedCovTable = '\n\n';
detailedCovTable += tb.createTable(
Expand Down
59 changes: 36 additions & 23 deletions packages/apex-node/src/tests/testService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
ApexTestRunResultStatus,
TestResult,
ApexCodeCoverage,
PerTestCoverage,
PerClassCoverage,
OutputDirConfig
} from './types';
import * as util from 'util';
Expand Down Expand Up @@ -108,15 +108,17 @@ export class TestService {
};

if (codeCoverage) {
const perTestCovMap = await this.getPerTestCodeCoverage(
const perClassCovMap = await this.getPerClassCodeCoverage(
apexTestClassIdSet
);

result.tests.forEach(item => {
const keyCodeCov = `${item.apexClass.id}-${item.methodName}`;
const perTestCov = perTestCovMap.get(keyCodeCov);
coveredApexClassIdSet.add(perTestCov.apexClassOrTriggerId);
item.perTestCoverage = perTestCov;
const perClassCov = perClassCovMap.get(keyCodeCov);
perClassCov.forEach(classCov =>
coveredApexClassIdSet.add(classCov.apexClassOrTriggerId)
);
item.perClassCoverage = perClassCov;
});

const {
Expand Down Expand Up @@ -297,17 +299,19 @@ export class TestService {
};

if (codeCoverage) {
const perTestCovMap = await this.getPerTestCodeCoverage(
const perClassCovMap = await this.getPerClassCodeCoverage(
apexTestClassIdSet
);

result.tests.forEach(item => {
const keyCodeCov = `${item.apexClass.id}-${item.methodName}`;
const perTestCov = perTestCovMap.get(keyCodeCov);
// Skipped test is not in coverage map, check to see if perTestCov exists first
if (perTestCov) {
coveredApexClassIdSet.add(perTestCov.apexClassOrTriggerId);
item.perTestCoverage = perTestCov;
const perClassCov = perClassCovMap.get(keyCodeCov);
// Skipped test is not in coverage map, check to see if perClassCov exists first
if (perClassCov) {
perClassCov.forEach(classCov =>
coveredApexClassIdSet.add(classCov.apexClassOrTriggerId)
);
item.perClassCoverage = perClassCov;
}
});

Expand Down Expand Up @@ -445,31 +449,31 @@ export class TestService {
return `${orgWideCoverageResult.records[0].PercentCovered}%`;
}

public async getPerTestCodeCoverage(
//NOTE: a test could cover more than one class, map should contain a record for each covered class
public async getPerClassCodeCoverage(
apexTestClassSet: Set<string>
): Promise<Map<string, PerTestCoverage>> {
): Promise<Map<string, PerClassCoverage[]>> {
let str = '';
apexTestClassSet.forEach(elem => {
str += `'${elem}',`;
});
str = str.slice(0, -1);

const perTestCodeCovQuery =
const perClassCodeCovQuery =
'SELECT ApexTestClassId, ApexClassOrTrigger.Id, ApexClassOrTrigger.Name, TestMethodName, NumLinesCovered, NumLinesUncovered, Coverage FROM ApexCodeCoverage WHERE ApexTestClassId IN (%s)';
const perTestCodeCovResuls = (await this.connection.tooling.query(
util.format(perTestCodeCovQuery, `${str}`)
const perClassCodeCovResuls = (await this.connection.tooling.query(
util.format(perClassCodeCovQuery, `${str}`)
)) as ApexCodeCoverage;

const perTestCoverageMap = new Map<string, PerTestCoverage>();
perTestCodeCovResuls.records.forEach(item => {
const perClassCoverageMap = new Map<string, PerClassCoverage[]>();
perClassCodeCovResuls.records.forEach(item => {
const totalLines = item.NumLinesCovered + item.NumLinesUncovered;
const percentage = this.calculatePercentage(
item.NumLinesCovered,
totalLines
);

//NOTE: a test could cover more than one class, we should change this in order to handle that
perTestCoverageMap.set(`${item.ApexTestClassId}-${item.TestMethodName}`, {
const value = {
apexClassOrTriggerName: item.ApexClassOrTrigger.Name,
apexClassOrTriggerId: item.ApexClassOrTrigger.Id,
apexTestClassId: item.ApexTestClassId,
Expand All @@ -478,10 +482,19 @@ export class TestService {
numLinesUncovered: item.NumLinesUncovered,
percentage,
...(item.Coverage ? { coverage: item.Coverage } : {})
});
};
const key = `${item.ApexTestClassId}-${item.TestMethodName}`;
if (perClassCoverageMap.get(key)) {
perClassCoverageMap.get(key).push(value);
} else {
perClassCoverageMap.set(
`${item.ApexTestClassId}-${item.TestMethodName}`,
[value]
);
}
});

return perTestCoverageMap;
return perClassCoverageMap;
}

public async getAggregateCodeCoverage(
Expand Down Expand Up @@ -584,7 +597,7 @@ export class TestService {

if (codeCoverage) {
const coverageRecords = result.tests.map(record => {
return record.perTestCoverage;
return record.perClassCoverage;
});
fileMap.push({
path: join(
Expand Down
4 changes: 2 additions & 2 deletions packages/apex-node/src/tests/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ export type ApexTestResultData = {
/**
* The associated ApexCodeCoverage object
*/
perTestCoverage?: PerTestCoverage;
perClassCoverage?: PerClassCoverage[];
};

export type CodeCoverageResult = {
Expand Down Expand Up @@ -403,7 +403,7 @@ export type ApexCodeCoverage = {
records: ApexCodeCoverageRecord[];
};

export type PerTestCoverage = {
export type PerClassCoverage = {
apexClassOrTriggerName: string;
apexClassOrTriggerId: string;
apexTestClassId: string;
Expand Down
147 changes: 105 additions & 42 deletions packages/apex-node/test/tests/codeCoverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ describe('Get code coverage results', () => {
expect(coveredLines).to.equal(0);
});

it('should return per test code coverage', async () => {
const perTestCodeCovResult = [
it('should return per class code coverage for multiple test classes', async () => {
const perClassCodeCovResult = [
{
ApexClassOrTrigger: { Id: '0001x05958', Name: 'ApexTrigger1' },
TestMethodName: 'MethodOne',
Expand Down Expand Up @@ -194,63 +194,126 @@ describe('Get code coverage results', () => {
toolingQueryStub.resolves({
done: true,
totalSize: 3,
records: perTestCodeCovResult
records: perClassCodeCovResult
} as ApexCodeCoverage);

const testSrv = new TestService(mockConnection);
const perTestCoverageMap = await testSrv.getPerTestCodeCoverage(
const perClassCoverageMap = await testSrv.getPerClassCodeCoverage(
new Set<string>(['0001x05958', '0001x05959', '0001x05951'])
);
expect(perTestCoverageMap.size).to.eql(3);
expect(perTestCoverageMap.get('0001x05958-MethodOne')).to.deep.equal({
apexClassOrTriggerName: 'ApexTrigger1',
apexClassOrTriggerId: '0001x05958',
apexTestClassId: '0001x05958',
apexTestMethodName: 'MethodOne',
percentage: '83%',
coverage: { coveredLines: [1, 2, 3, 4, 5], uncoveredLines: [6] },
numLinesCovered: 5,
numLinesUncovered: 1
});
expect(perTestCoverageMap.get('0001x05959-MethodTwo')).to.deep.equal({
apexClassOrTriggerName: 'ApexTrigger2',
apexClassOrTriggerId: '0001x05959',
apexTestClassId: '0001x05959',
apexTestMethodName: 'MethodTwo',
percentage: '75%',
coverage: {
coveredLines: [1, 2, 3, 4, 5, 6],
uncoveredLines: [7, 8]
expect(perClassCoverageMap.size).to.eql(3);
expect(perClassCoverageMap.get('0001x05958-MethodOne')).to.deep.equal([
{
apexClassOrTriggerName: 'ApexTrigger1',
apexClassOrTriggerId: '0001x05958',
apexTestClassId: '0001x05958',
apexTestMethodName: 'MethodOne',
percentage: '83%',
coverage: { coveredLines: [1, 2, 3, 4, 5], uncoveredLines: [6] },
numLinesCovered: 5,
numLinesUncovered: 1
}
]);
expect(perClassCoverageMap.get('0001x05959-MethodTwo')).to.deep.equal([
{
apexClassOrTriggerName: 'ApexTrigger2',
apexClassOrTriggerId: '0001x05959',
apexTestClassId: '0001x05959',
apexTestMethodName: 'MethodTwo',
percentage: '75%',
coverage: {
coveredLines: [1, 2, 3, 4, 5, 6],
uncoveredLines: [7, 8]
},
numLinesCovered: 6,
numLinesUncovered: 2
}
]);
expect(perClassCoverageMap.get('0001x05951-MethodThree')).to.deep.equal([
{
apexClassOrTriggerName: 'ApexTrigger3',
apexClassOrTriggerId: '0001x05951',
apexTestClassId: '0001x05951',
apexTestMethodName: 'MethodThree',
percentage: '70%',
coverage: {
coveredLines: [1, 2, 3, 4, 5, 6, 7],
uncoveredLines: [8, 9, 10]
},
numLinesCovered: 7,
numLinesUncovered: 3
}
]);
});

it('should return per class code coverage for test that covers multiple classes', async () => {
const perClassCodeCovResult = [
{
ApexClassOrTrigger: { Id: '0001x05958', Name: 'ApexTrigger1' },
TestMethodName: 'MethodOne',
ApexTestClassId: '0001x05958',
NumLinesCovered: 5,
NumLinesUncovered: 1,
Coverage: { coveredLines: [1, 2, 3, 4, 5], uncoveredLines: [6] }
},
numLinesCovered: 6,
numLinesUncovered: 2
});
expect(perTestCoverageMap.get('0001x05951-MethodThree')).to.deep.equal({
apexClassOrTriggerName: 'ApexTrigger3',
apexClassOrTriggerId: '0001x05951',
apexTestClassId: '0001x05951',
apexTestMethodName: 'MethodThree',
percentage: '70%',
coverage: {
coveredLines: [1, 2, 3, 4, 5, 6, 7],
uncoveredLines: [8, 9, 10]
{
ApexClassOrTrigger: { Id: '0001x05959', Name: 'ApexTrigger2' },
ApexTestClassId: '0001x05958',
TestMethodName: 'MethodOne',
NumLinesCovered: 6,
NumLinesUncovered: 2,
Coverage: { coveredLines: [1, 2, 3, 4, 5, 6], uncoveredLines: [7, 8] }
}
];
toolingQueryStub.resolves({
done: true,
totalSize: 2,
records: perClassCodeCovResult
} as ApexCodeCoverage);

const testSrv = new TestService(mockConnection);
const perClassCoverageMap = await testSrv.getPerClassCodeCoverage(
new Set<string>(['0001x05958'])
);
expect(perClassCoverageMap.size).to.eql(1);
expect(perClassCoverageMap.get('0001x05958-MethodOne')).to.deep.equal([
{
apexClassOrTriggerName: 'ApexTrigger1',
apexClassOrTriggerId: '0001x05958',
apexTestClassId: '0001x05958',
apexTestMethodName: 'MethodOne',
percentage: '83%',
coverage: { coveredLines: [1, 2, 3, 4, 5], uncoveredLines: [6] },
numLinesCovered: 5,
numLinesUncovered: 1
},
numLinesCovered: 7,
numLinesUncovered: 3
});
{
apexClassOrTriggerName: 'ApexTrigger2',
apexClassOrTriggerId: '0001x05959',
apexTestClassId: '0001x05958',
apexTestMethodName: 'MethodOne',
percentage: '75%',
coverage: {
coveredLines: [1, 2, 3, 4, 5, 6],
uncoveredLines: [7, 8]
},
numLinesCovered: 6,
numLinesUncovered: 2
}
]);
});

it('should return per test coverage', async () => {
it('should return per class coverage for test that covers 0 classes', async () => {
toolingQueryStub.resolves({
done: true,
totalSize: 0,
records: []
} as ApexCodeCoverage);
const testSrv = new TestService(mockConnection);
const perTestCoverageMap = await testSrv.getPerTestCodeCoverage(
const perClassCoverageMap = await testSrv.getPerClassCodeCoverage(
new Set<string>([])
);

expect(perTestCoverageMap.size).to.equal(0);
expect(perClassCoverageMap.size).to.equal(0);
});
});
Loading

0 comments on commit abe8149

Please sign in to comment.