.NET中JIRA服务单元测试:无法Mock Atlassian.Jira.Jira类
问题:Mock Atlassian.Jira的Jira类时遇到无参构造函数缺失错误
我开发了TcJiraService类,负责JIRA工单的获取与创建,基于Atlassian.Jira包的Jira客户端实现,业务代码运行正常,但编写单元测试时遇到问题:用Moq框架Mock Jira类时抛出System.ArgumentException,提示无法实例化代理,找不到无参构造函数。即使改用接口依赖注入,问题依然存在。之前用HttpClient连接JIRA时测试正常,现在求解决方法。
业务代码
public class TcJiraService(Jira tJiraClient, IOptions<TcJiraOptions> tOptions, ILogger<TcJiraService> tLogger) { private readonly TcJiraOptions mOptions = tOptions.Value; public async Task<List<Issue>> GetJiraIssuesAsync() { try { var aJqlQuery = $"project = '{mOptions.ProjectKey}'"; var aIssues = await tJiraClient.Issues.GetIssuesFromJqlAsync(aJqlQuery); foreach (var aIssue in aIssues) { tLogger.LogInformation("Issue Key: {aIssue.Key}, Summary: {aIssue.Summary}", aIssue.Key, aIssue.Summary); } return aIssues.ToList(); } catch (Exception aEx) { tLogger.LogError("An error occurred while fetching issues from JIRA: {Message}", aEx.Message); return []; } } public async Task<string?> CreateJiraIssueAsync(string? tSummary, string? tDescription, string? tEmailSender) { try { var aIssue = tJiraClient.CreateIssue(mOptions.ProjectKey); aIssue.Type = new IssueType("9"); aIssue.Priority = new IssuePriority("5"); aIssue.Summary = tSummary; aIssue.Description = tDescription; await aIssue.SaveChangesAsync(); return aIssue.Key.Value; } catch (Exception aEx) { tLogger.LogError("Failed to create JIRA issue: {Message}", aEx.Message); return null; } } public async Task<bool> AttachContentToIssueAsync(string tIssueKey, byte[] tContentData, string tFileName) { try { var aIssue = await tJiraClient.Issues.GetIssueAsync(tIssueKey); var aAttachment = new UploadAttachmentInfo(tFileName, tContentData); await aIssue.AddAttachmentAsync([aAttachment]); return true; } catch (Exception aEx) { tLogger.LogError("Failed to attach file to JIRA issue. {Message}", aEx.Message); return false; } } }
测试代码
public class TcJiraServiceTest { private readonly Mock<Jira> mockJiraClient = new Mock<Jira>(); private readonly Mock<ILogger<TcJiraService>> mockLogger = new Mock<ILogger<TcJiraService>>(); private readonly IOptions<TcJiraOptions> mockOptions = Options.Create(new TcJiraOptions { ProjectKey = "TEST-1", BaseUrl = "example.com" }); private TcJiraService service; public TcJiraServiceTest() { this.service = new TcJiraService(mockJiraClient.Object, mockOptions, mockLogger.Object); } [Fact] public async Task CreateJiraIssueAsync_ReturnsIssueKey_WhenCalledWithValidParameters() { // Arrange var fakeIssue = new Mock<Issue>(mockJiraClient.Object, "TEST", "1"); fakeIssue.Setup(issue => issue.Key).Returns("TEST-1"); // Act var issueKey = await service.CreateJiraIssueAsync("Test Summary", "Test Description", "test@example.com"); // Assert Assert.Equal("TEST-1", issueKey); } }
错误信息
System.ArgumentException: Can not instantiate proxy of class: Atlassian.Jira.Jira. System.ArgumentException Can not instantiate proxy of class: Atlassian.Jira.Jira. Could not find a parameterless constructor. (Parameter 'constructorArguments') at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyInstance(Type proxyType, List`1 proxyArguments, Type classToProxy, Object[] constructorArguments) at Castle.DynamicProxy.ProxyGenerator.CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, Object[] constructorArguments, IInterceptor[] interceptors) at Moq.CastleProxyFactory.CreateProxy(Type mockType, IInterceptor interceptor, Type[] interfaces, Object[] arguments) in /_/src/Moq/Interception/CastleProxyFactory.cs:line 110 at Moq.Mock`1.InitializeInstance() in /_/src/Moq/Mock`1.cs:line 502 at Moq.Mock`1.OnGetObject() in /_/src/Moq/Mock`1.cs:line 516 at Moq.Mock.get_Object() in /_/src/Moq/Mock.cs:line 180 at Moq.Mock`1.get_Object() in /_/src/Moq/Mock`1.cs:line 453 at TcJiraServiceTest..ctor() in C:\Users\vuso1\RiderProjects\New\Neuer Ordner\trumpf.mailserveraccess\src\Trumpf.MailServerAccess.Tests\TcJiraServiceTest.cs:line 22 at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean wrapExceptions) System.MissingMethodException Constructor on type 'Castle.Proxies.JiraProxy' not found. at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture) at Castle.DynamicProxy.ProxyGenerator.CreateClassProxyInstance(Type proxyType, List`1 proxyArguments, Type classToProxy, Object[] constructorArguments)
解决方案
1. 解决Jira类无法Mock的核心问题
Moq动态代理需要类有可访问的无参构造函数,而Atlassian.Jira的Jira类没有提供。有两种可行方案:
方案A:给Mock传入构造函数参数
如果Jira类有公开的带参构造函数,创建Mock时传入匹配的假参数,让Moq能实例化代理类:
// 示例:匹配Jira类的Uri+Credential构造函数 var mockJiraClient = new Mock<Jira>(new Uri("https://fake-jira-instance.com"), new NetworkCredential("fake-user", "fake-pass"));
注意:参数必须严格匹配Jira类已有的构造函数签名,可以查看Atlassian.Jira的源码或文档确认。
方案B:抽象依赖(推荐长期方案)
自己定义接口封装TcJiraService需要的Jira客户端能力,通过适配器类对接真实的Jira客户端,让业务类依赖接口而非具体实现:
// 1. 定义接口,只暴露业务需要的方法 public interface IJiraClient { Task<IEnumerable<Issue>> GetIssuesFromJqlAsync(string jql); Issue CreateIssue(string projectKey); Task<Issue> GetIssueAsync(string issueKey); } // 2. 实现适配器,对接真实Jira客户端 public class JiraClientAdapter : IJiraClient { private readonly Jira _innerJira; public JiraClientAdapter(Jira innerJira) { _innerJira = innerJira; } public Task<IEnumerable<Issue>> GetIssuesFromJqlAsync(string jql) { return _innerJira.Issues.GetIssuesFromJqlAsync(jql); } public Issue CreateIssue(string projectKey) { return _innerJira.CreateIssue(projectKey); } public Task<Issue> GetIssueAsync(string issueKey) { return _innerJira.Issues.GetIssueAsync(issueKey); } } // 3. 修改TcJiraService的依赖为IJiraClient public class TcJiraService(IJiraClient tJiraClient, IOptions<TcJiraOptions> tOptions, ILogger<TcJiraService> tLogger) { // 原有业务逻辑不变,仅依赖类型替换 }
测试时直接Mock接口,完全避开Jira类的构造函数问题:
private readonly Mock<IJiraClient> mockJiraClient = new Mock<IJiraClient>();
2. 修正Issue的Mock问题
原测试中MockIssue也会遇到相同的构造函数限制,建议直接创建假对象而非Mock:
// 直接实例化Issue(如果允许) var fakeIssue = new Issue(mockJiraClient.Object, "TEST", "1") { Key = "TEST-1" }; // 若属性是只读,可通过反射设置
3. 补全测试的Setup逻辑
原测试没有对CreateIssue和SaveChangesAsync做Mock,导致调用时会走真实逻辑,必须补充:
[Fact] public async Task CreateJiraIssueAsync_ReturnsIssueKey_WhenCalledWithValidParameters() { // Arrange var fakeIssue = new Mock<Issue>(mockJiraClient.Object, "TEST", "1"); fakeIssue.Setup(issue => issue.Key).Returns("TEST-1"); // Mock CreateIssue方法返回假Issue mockJiraClient.Setup(client => client.CreateIssue(It.IsAny<string>())) .Returns(fakeIssue.Object); // Mock SaveChangesAsync为成功 fakeIssue.Setup(issue => issue.SaveChangesAsync(default)) .Returns(Task.CompletedTask); // Act var issueKey = await service.CreateJiraIssueAsync("Test Summary", "Test Description", "test@example.com"); // Assert Assert.Equal("TEST-1", issueKey); }
内容的提问来源于stack exchange,提问作者Haidepzai
相关产品推荐
相关产品推荐

