Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handle test method covering multiple classes #122

Merged
merged 3 commits into from
Jan 19, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ? elem.message : '',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can try out the handy null coalescing feature in TS 3.7 here - elem.message ?? ''

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh fancy!

AnanyaJha marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -580,7 +593,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 @@ -165,8 +165,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 @@ -198,63 +198,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